示例#1
0
文件: signal.py 项目: CBuiVNG/signals
class PublicSignalCreateSerializer(SignalValidationMixin,
                                   serializers.ModelSerializer):
    """
    This serializer allows anonymous users to report `signals.Signals`.

    Note: this is only used in the creation of new Signal instances, not to
    create the response body after a succesfull POST.
    """
    location = _NestedLocationModelSerializer()
    reporter = _NestedReporterModelSerializer()
    category = _NestedCategoryModelSerializer(source='category_assignment')

    extra_properties = SignalExtraPropertiesField(
        required=False,
        allow_null=True,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    incident_date_start = serializers.DateTimeField()

    class Meta(object):
        model = Signal
        fields = (
            'text',
            'text_extra',
            'location',
            'category',
            'reporter',
            'incident_date_start',
            'incident_date_end',
            'extra_properties',
        )

    def validate(self, data):
        """Make sure any extra data is rejected"""
        if hasattr(self, 'initial_data'):
            present_keys = set(self.initial_data)
            allowed_keys = set(self.fields)

            if present_keys - allowed_keys:
                raise ValidationError('Extra properties present: {}'.format(
                    ', '.join(present_keys - allowed_keys)))
        return super(PublicSignalCreateSerializer, self).validate(data)

    def create(self, validated_data):
        location_data = validated_data.pop('location')
        reporter_data = validated_data.pop('reporter')
        category_assignment_data = validated_data.pop('category_assignment')

        status_data = {"state": workflow.GEMELD}
        signal = Signal.actions.create_initial(validated_data, location_data,
                                               status_data,
                                               category_assignment_data,
                                               reporter_data)
        return signal
示例#2
0
class PublicSignalCreateSerializer(serializers.ModelSerializer):
    """
    This serializer allows anonymous users to report `signals.Signals`.

    Note: this is only used in the creation of new Signal instances, not to
    create the response body after a succesfull POST.
    """
    status = _NestedStatusModelSerializer(required=False, )
    location = _NestedLocationModelSerializer()
    reporter = _NestedReporterModelSerializer()
    category = _NestedCategoryModelSerializer(source='category_assignment')

    country = CountrySerializer(required=False)
    city = CitySerializer(required=False)
    city_object = CityObjectSerializer(many=True, required=False)

    updates = SignalPlanUpdateSerializer(many=True, required=False)

    extra_properties = SignalExtraPropertiesField(
        required=False,
        allow_null=True,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    incident_date_start = serializers.DateTimeField()

    #id_mapping = IDMappingSerializer(required=False)

    class Meta(object):
        model = Signal
        fields = (
            'text',
            'text_extra',
            'signal_id',
            'location',
            'category',
            'reporter',
            'incident_date_start',
            'incident_date_end',
            'status',
            'source',
            'extra_properties',
            'country',
            'city',
            'city_object',
            'webform_kenmark',
            'mb_report_id',
            'facilitator_report_id',
            'report_days',
            'forman_emp_name',
            'urgency',
            'plan_time',
            'updates',
            'updated_by',
        )

    def validate(self, data):
        """Make sure any extra data is rejected"""
        if hasattr(self, 'initial_data'):
            present_keys = set(self.initial_data)
            allowed_keys = set(self.fields)

            if present_keys - allowed_keys:
                raise ValidationError('Extra properties present: {}'.format(
                    ', '.join(present_keys - allowed_keys)))
        return data

    def create(self, validated_data):
        print("Validated data : ", validated_data)
        location_data = validated_data.pop('location')
        reporter_data = validated_data.pop('reporter')
        category_assignment_data = validated_data.pop('category_assignment')
        country_data = validated_data.pop('country', None)
        city_data = validated_data.pop('city', None)
        city_object_data = validated_data.pop('city_object', None)
        status_data = validated_data.pop('status', None)
        if not status_data:
            status_data = {"state": workflow.GEMELD}

        signal = Signal.actions.create_initial(validated_data, location_data,
                                               status_data,
                                               category_assignment_data,
                                               reporter_data, country_data,
                                               city_data, city_object_data)
        return signal
示例#3
0
class PrivateSignalSerializerDetail(HALSerializer, AddressValidationMixin):
    """
    This serializer is used for the detail endpoint and when updating the instance
    """
    serializer_url_field = PrivateSignalLinksFieldWithArchives
    _display = DisplayField()

    country = CountrySerializer(required=False,
                                permission_classes=(SIAPermissions, ))

    city = CitySerializer(required=False,
                          permission_classes=(SIAPermissions, ))

    city_object = CityObjectSerializer(many=True, required=False)

    location = _NestedLocationModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    status = _NestedStatusModelSerializer(
        required=False, permission_classes=(SignalChangeStatusPermission, ))

    category = _NestedCategoryModelSerializer(
        source='category_assignment',
        required=False,
        permission_classes=(SignalChangeCategoryPermission, ))

    reporter = _NestedReporterModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    priority = _NestedPriorityModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    notes = _NestedNoteModelSerializer(
        many=True,
        required=False,
        permission_classes=(SignalCreateNotePermission, ))

    type = _NestedTypeModelSerializer(
        required=False,
        permission_classes=(SIAPermissions, ),
        source='type_assignment',
    )

    directing_departments = _NestedDepartmentModelSerializer(
        source='directing_departments_assignment.departments',
        many=True,
        required=False,
        permission_classes=(SIAPermissions, ),
    )

    updates = SignalPlanUpdateSerializer(many=True, required=False)

    mb_mapping = serializers.SerializerMethodField(required=False)
    has_attachments = serializers.SerializerMethodField()
    image_category = ImageCategorySerializer(required=False)

    extra_properties = SignalExtraPropertiesField(
        required=False,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])  # noqa

    class Meta:
        model = Signal
        fields = (
            '_links',
            '_display',
            'category',
            'id',
            'has_attachments',
            'location',
            'status',
            'reporter',
            'priority',
            'notes',
            'type',
            'source',
            'text',
            'text_extra',
            'extra_properties',
            'created_at',
            'updated_at',
            'incident_date_start',
            'incident_date_end',
            'finished_by',
            'directing_departments',
            'country',
            'city',
            'city_object',
            'webform_kenmark',
            'mb_report_id',
            'facilitator_report_id',
            'report_days',
            'forman_emp_name',
            'urgency',
            'plan_time',
            'updates',
            'mb_mapping',
            'updated_by',
            'image_category',
        )
        read_only_fields = (
            'id',
            'has_attachments',
        )

    def get_mb_mapping(self, obj):
        if IDMapping.objects.filter(seda_signal_id=obj.signal_id).exists():
            return IDMappingSerializer(
                IDMapping.objects.get(seda_signal_id=obj.signal_id)).data

        return None

    def get_has_attachments(self, obj):
        return obj.attachments.exists()

    def update(self, instance, validated_data):
        """
        Perform update on nested models.

        Note:
        - Reporter cannot be updated via the API.
        - Atomic update (all fail/succeed), django signals on full success (see
          underlying update_multiple method of actions SignalManager).
        """
        if not instance.is_parent() and validated_data.get(
                'directing_departments_assignment') is not None:
            raise serializers.ValidationError(
                'Directing departments can only be set on a parent Signal')

        user_email = self.context['request'].user.email

        for _property in [
                'location', 'status', 'category_assignment', 'priority'
        ]:
            if _property in validated_data:
                data = validated_data[_property]
                data['created_by'] = user_email

        if 'type_assignment' in validated_data:
            type_data = validated_data.pop('type_assignment')
            type_data['created_by'] = user_email
            validated_data['type'] = type_data

        if 'notes' in validated_data and validated_data['notes']:
            note_data = validated_data['notes'][0]
            note_data['created_by'] = user_email

        if 'directing_departments_assignment' in validated_data and validated_data[
                'directing_departments_assignment']:
            validated_data['directing_departments_assignment'][
                'created_by'] = user_email

        signal = Signal.actions.update_multiple(validated_data, instance)
        return signal
示例#4
0
class PrivateSignalSerializerList(HALSerializer, AddressValidationMixin):
    """
    This serializer is used for the list endpoint and when creating a new instance
    """
    serializer_url_field = PrivateSignalLinksField
    _display = DisplayField()

    country = CountrySerializer(required=False,
                                permission_classes=(SIAPermissions, ))

    city = CitySerializer(required=False,
                          permission_classes=(SIAPermissions, ))

    city_object = CityObjectSerializer(many=True, required=False)

    location = _NestedLocationModelSerializer(
        permission_classes=(SIAPermissions, ))

    status = _NestedStatusModelSerializer(
        required=False, permission_classes=(SignalCreateInitialPermission, ))

    category = _NestedCategoryModelSerializer(
        source='category_assignment',
        permission_classes=(SignalCreateInitialPermission, ))

    reporter = _NestedReporterModelSerializer(
        permission_classes=(SIAPermissions, ))

    priority = _NestedPriorityModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    notes = _NestedNoteModelSerializer(
        many=True,
        required=False,
        permission_classes=(SignalCreateInitialPermission, ))

    type = _NestedTypeModelSerializer(
        required=False,
        permission_classes=(SIAPermissions, ),
        source='type_assignment',
    )

    updates = SignalPlanUpdateSerializer(many=True, required=False)

    directing_departments = _NestedDepartmentModelSerializer(
        source='directing_departments_assignment.departments',
        many=True,
        required=False,
        permission_classes=(SIAPermissions, ),
    )

    has_attachments = serializers.SerializerMethodField()

    extra_properties = SignalExtraPropertiesField(
        required=False,
        allow_null=True,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    class Meta:
        model = Signal
        fields = (
            '_links',
            '_display',
            'id',
            'signal_id',
            'source',
            'text',
            'text_extra',
            'status',
            'location',
            'category',
            'reporter',
            'priority',
            'type',
            'created_at',
            'updated_at',
            'incident_date_start',
            'incident_date_end',
            'operational_date',
            'has_attachments',
            'extra_properties',
            'notes',
            'directing_departments',
            'finished_by',
            'country',
            'city',
            'city_object',
            'webform_kenmark',
            'mb_report_id',
            'facilitator_report_id',
            'report_days',
            'forman_emp_name',
            'urgency',
            'plan_time',
            'updates',
            'updated_by',
        )
        read_only_fields = (
            'created_at',
            'updated_at',
            'has_attachments',
        )
        extra_kwargs = {
            'source': {
                'validators': [SignalSourceValidator()]
            },
        }

    def get_has_attachments(self, obj):
        return obj.attachments.exists()

    def create(self, validated_data):
        if validated_data.get('directing_departments_assignment') is not None:
            raise serializers.ValidationError(
                'Directing departments cannot be set on initial creation')

        if validated_data.get('status') is not None:
            raise serializers.ValidationError(
                "Status cannot be set on initial creation")

        # Set default status
        logged_in_user = self.context['request'].user
        INITIAL_STATUS = {
            'state': workflow.GEMELD,  # see models.py is already default
            'text': None,
            'user': logged_in_user.email,
        }

        # We require location and reporter to be set and to be valid.
        reporter_data = validated_data.pop('reporter')

        location_data = validated_data.pop('location')
        location_data['created_by'] = logged_in_user.email

        category_assignment_data = validated_data.pop('category_assignment')
        category_assignment_data['created_by'] = logged_in_user.email

        # We will use the priority and signal type on the incoming message if present.
        priority_data = validated_data.pop(
            'priority', {'priority': Priority.PRIORITY_NORMAL})
        priority_data['created_by'] = logged_in_user.email
        type_data = validated_data.pop('type_assignment', {})
        type_data['created_by'] = logged_in_user.email

        country_data = validated_data.pop('country', None)
        city_data = validated_data.pop('city', None)
        city_object_data = validated_data.pop('city_object', None)

        signal = Signal.actions.create_initial(validated_data, location_data,
                                               INITIAL_STATUS,
                                               category_assignment_data,
                                               reporter_data, country_data,
                                               city_data, city_object_data,
                                               priority_data, type_data)

        return signal
示例#5
0
文件: signal.py 项目: CBuiVNG/signals
class PrivateSignalSerializerList(SignalValidationMixin, HALSerializer):
    """
    This serializer is used for the list endpoint and when creating a new instance
    """
    serializer_url_field = PrivateSignalLinksField
    _display = DisplayField()

    location = _NestedLocationModelSerializer(
        permission_classes=(SIAPermissions, ))

    status = _NestedStatusModelSerializer(
        required=False, permission_classes=(SignalCreateInitialPermission, ))

    category = _NestedCategoryModelSerializer(
        source='category_assignment',
        permission_classes=(SignalCreateInitialPermission, ))

    reporter = _NestedReporterModelSerializer(
        permission_classes=(SIAPermissions, ))

    priority = _NestedPriorityModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    notes = _NestedNoteModelSerializer(
        many=True,
        required=False,
        permission_classes=(SignalCreateInitialPermission, ))

    type = _NestedTypeModelSerializer(
        required=False,
        permission_classes=(SIAPermissions, ),
        source='type_assignment',
    )

    directing_departments = _NestedDepartmentModelSerializer(
        source='directing_departments_assignment.departments',
        many=True,
        required=False,
        permission_classes=(SIAPermissions, ),
    )

    routing_departments = _NestedDepartmentModelSerializer(
        source='routing_assignment.departments',
        many=True,
        required=False,
        allow_null=True,
        permission_classes=(SIAPermissions, ),
    )

    has_attachments = serializers.SerializerMethodField()

    assigned_user_id = serializers.IntegerField(
        source='user_assignment.user.id', required=False, allow_null=True)

    extra_properties = SignalExtraPropertiesField(
        required=False,
        allow_null=True,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    parent = serializers.PrimaryKeyRelatedField(required=False,
                                                read_only=False,
                                                write_only=True,
                                                queryset=Signal.objects.all())
    has_parent = serializers.SerializerMethodField()
    has_children = serializers.SerializerMethodField()

    attachments = PrivateSignalAttachmentRelatedField(
        view_name='private-signals-attachments-detail',
        many=True,
        required=False,
        read_only=False,
        write_only=True,
        queryset=Attachment.objects.all())

    class Meta:
        model = Signal
        list_serializer_class = _SignalListSerializer
        fields = ('_links', '_display', 'id', 'signal_id', 'source', 'text',
                  'text_extra', 'status', 'location', 'category', 'reporter',
                  'priority', 'type', 'created_at', 'updated_at',
                  'incident_date_start', 'incident_date_end',
                  'operational_date', 'has_attachments', 'extra_properties',
                  'notes', 'directing_departments', 'routing_departments',
                  'attachments', 'parent', 'has_parent', 'has_children',
                  'assigned_user_id')
        read_only_fields = (
            'created_at',
            'updated_at',
            'has_attachments',
            'has_parent',
            'has_children',
        )
        extra_kwargs = {
            'source': {
                'validators': [SignalSourceValidator()]
            },
        }

    def get_has_attachments(self, obj):
        return obj.attachments.exists()

    def get_has_parent(self, obj):
        return obj.parent_id is not None  # True is a parent_id is set, False if not

    def get_has_children(self, obj):
        return obj.children.exists()

    def validate(self, attrs):
        errors = {}
        if attrs.get('directing_departments_assignment') is not None:
            errors.update({
                'directing_departments_assignment':
                ['Directing departments cannot be set on initial creation']
            })

        if attrs.get('routing_assignment') is not None:
            errors.update({
                'routing_assignment': [
                    'Signal departments relation cannot be set on initial creation'
                ]
            })

        if attrs.get('status') is not None:
            errors.update(
                {'status': ['Status cannot be set on initial creation']})

        attachments = attrs.get('attachments')
        parent = attrs.get('parent')
        if attachments and parent is None:
            errors.update({
                'attachments': [
                    'Attachments can only be copied when creating a child Signal'
                ]
            })

        if attachments and parent:
            attachments_belong_to_parent = all([
                parent.pk == attachment._signal_id
                for attachment in attachments
            ])
            if not attachments_belong_to_parent:
                errors.update({
                    'attachments':
                    ['Attachments can only be copied from the parent Signal']
                })

        if errors:
            raise serializers.ValidationError(errors)

        return super(PrivateSignalSerializerList, self).validate(attrs=attrs)

    def create(self, validated_data):
        # Set default status
        logged_in_user = self.context['request'].user
        INITIAL_STATUS = {
            'state': workflow.GEMELD,  # see models.py is already default
            'text': None,
            'user': logged_in_user.email,
        }

        # We require location and reporter to be set and to be valid.
        reporter_data = validated_data.pop('reporter')

        location_data = validated_data.pop('location')
        location_data['created_by'] = logged_in_user.email

        category_assignment_data = validated_data.pop('category_assignment')
        category_assignment_data['created_by'] = logged_in_user.email

        # We will use the priority and signal type on the incoming message if present.
        priority_data = validated_data.pop(
            'priority', {'priority': Priority.PRIORITY_NORMAL})
        priority_data['created_by'] = logged_in_user.email
        type_data = validated_data.pop('type_assignment', {})
        type_data['created_by'] = logged_in_user.email

        attachments = validated_data.pop(
            'attachments') if 'attachments' in validated_data else None

        signal = Signal.actions.create_initial(validated_data, location_data,
                                               INITIAL_STATUS,
                                               category_assignment_data,
                                               reporter_data, priority_data,
                                               type_data)

        if attachments:
            Signal.actions.copy_attachments(data=attachments, signal=signal)

        signal.refresh_from_db()
        return signal
示例#6
0
文件: signal.py 项目: CBuiVNG/signals
class PrivateSignalSerializerDetail(HALSerializer, AddressValidationMixin):
    """
    This serializer is used for the detail endpoint and when updating the instance
    """
    serializer_url_field = PrivateSignalLinksFieldWithArchives
    _display = DisplayField()

    location = _NestedLocationModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    status = _NestedStatusModelSerializer(
        required=False, permission_classes=(SignalChangeStatusPermission, ))

    category = _NestedCategoryModelSerializer(
        source='category_assignment',
        required=False,
        permission_classes=(SignalChangeCategoryPermission, ))

    reporter = _NestedReporterModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    priority = _NestedPriorityModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    notes = _NestedNoteModelSerializer(
        many=True,
        required=False,
        permission_classes=(SignalCreateNotePermission, ))

    type = _NestedTypeModelSerializer(
        required=False,
        permission_classes=(SIAPermissions, ),
        source='type_assignment',
    )

    directing_departments = _NestedDepartmentModelSerializer(
        source='directing_departments_assignment.departments',
        many=True,
        required=False,
        permission_classes=(SIAPermissions, ),
    )

    routing_departments = _NestedDepartmentModelSerializer(
        source='routing_assignment.departments',
        many=True,
        required=False,
        allow_null=True,
        permission_classes=(SIAPermissions, ),
    )

    has_attachments = serializers.SerializerMethodField()

    assigned_user_id = serializers.IntegerField(
        source='user_assignment.user.id', required=False, allow_null=True)

    extra_properties = SignalExtraPropertiesField(
        required=False,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    attachments = PrivateSignalAttachmentRelatedField(
        view_name='private-signals-attachments-detail',
        many=True,
        required=False,
        read_only=True)

    class Meta:
        model = Signal
        fields = (
            '_links',
            '_display',
            'category',
            'id',
            'has_attachments',
            'location',
            'status',
            'reporter',
            'priority',
            'notes',
            'type',
            'source',
            'text',
            'text_extra',
            'extra_properties',
            'created_at',
            'updated_at',
            'incident_date_start',
            'incident_date_end',
            'directing_departments',
            'routing_departments',
            'attachments',
            'assigned_user_id',
        )
        read_only_fields = (
            'id',
            'has_attachments',
        )

    def get_has_attachments(self, obj):
        return obj.attachments.exists()

    def update(self, instance, validated_data):  # noqa
        """
        Perform update on nested models.

        Note:
        - Reporter cannot be updated via the API.
        - Atomic update (all fail/succeed), django signals on full success (see
          underlying update_multiple method of actions SignalManager).
        """
        if not instance.is_parent() and validated_data.get(
                'directing_departments_assignment') is not None:
            raise serializers.ValidationError(
                'Directing departments can only be set on a parent Signal')

        user_email = self.context['request'].user.email

        for _property in [
                'location', 'status', 'category_assignment', 'priority'
        ]:
            if _property in validated_data:
                data = validated_data[_property]
                data['created_by'] = user_email

        if 'type_assignment' in validated_data:
            type_data = validated_data.pop('type_assignment')
            type_data['created_by'] = user_email
            validated_data['type'] = type_data

        if 'notes' in validated_data and validated_data['notes']:
            note_data = validated_data['notes'][0]
            note_data['created_by'] = user_email

        if 'directing_departments_assignment' in validated_data and validated_data[
                'directing_departments_assignment']:
            validated_data['directing_departments_assignment'][
                'created_by'] = user_email

        if 'routing_assignment' in validated_data and validated_data[
                'routing_assignment']:
            validated_data['routing_assignment']['created_by'] = user_email

        if 'user_assignment' in validated_data and validated_data[
                'user_assignment']:
            validated_data['created_by'] = user_email

        signal = Signal.actions.update_multiple(validated_data, instance)
        return signal
示例#7
0
class PublicSignalCreateSerializer(serializers.ModelSerializer):
    """
    This serializer allows anonymous users to report `signals.Signals`.
    """
    location = _NestedLocationModelSerializer()
    reporter = _NestedReporterModelSerializer()
    status = _NestedStatusModelSerializer(required=False)
    category = _NestedCategoryModelSerializer(source='category_assignment')
    priority = _NestedPriorityModelSerializer(required=False, read_only=True)
    attachments = _NestedAttachmentModelSerializer(many=True, read_only=True)

    extra_properties = SignalExtraPropertiesField(
        required=False,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])

    incident_date_start = serializers.DateTimeField()

    class Meta(object):
        model = Signal
        fields = (
            'id',
            'signal_id',
            'source',
            'text',
            'text_extra',
            'location',
            'category',
            'reporter',
            'status',
            'priority',
            'created_at',
            'updated_at',
            'incident_date_start',
            'incident_date_end',
            'operational_date',
            'image',
            'attachments',
            'extra_properties',
        )
        read_only_fields = (
            'id',
            'signal_id',
            'created_at',
            'updated_at',
            'status',
            'image'
            'attachments',
        )
        extra_kwargs = {
            'id': {
                'label': 'ID'
            },
            'signal_id': {
                'label': 'SIGNAL_ID'
            },
            'source': {
                'validators': [SignalSourceValidator()]
            },
        }

    def create(self, validated_data):
        if validated_data.get('status') is not None:
            raise serializers.ValidationError(
                "Status cannot be set on initial creation")

        location_data = validated_data.pop('location')
        reporter_data = validated_data.pop('reporter')
        category_assignment_data = validated_data.pop('category_assignment')

        status_data = {"state": workflow.GEMELD}
        signal = Signal.actions.create_initial(validated_data, location_data,
                                               status_data,
                                               category_assignment_data,
                                               reporter_data)
        return signal
示例#8
0
class PrivateSignalSerializerDetail(HALSerializer, AddressValidationMixin):
    """
    This serializer is used for the detail endpoint and when updating the instance
    """
    serializer_url_field = PrivateSignalLinksFieldWithArchives
    _display = DisplayField()

    location = _NestedLocationModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    status = _NestedStatusModelSerializer(
        required=False, permission_classes=(SignalChangeStatusPermission, ))

    category = _NestedCategoryModelSerializer(
        source='category_assignment',
        required=False,
        permission_classes=(SignalChangeCategoryPermission, ))

    reporter = _NestedReporterModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    priority = _NestedPriorityModelSerializer(
        required=False, permission_classes=(SIAPermissions, ))

    notes = _NestedNoteModelSerializer(
        many=True,
        required=False,
        permission_classes=(SignalCreateNotePermission, ))

    has_attachments = serializers.SerializerMethodField()

    extra_properties = SignalExtraPropertiesField(
        required=False,
        validators=[
            ExtraPropertiesValidator(
                filename=os.path.join(os.path.dirname(__file__), '..',
                                      'json_schema', 'extra_properties.json'))
        ])  # noqa

    class Meta:
        model = Signal
        fields = (
            '_links',
            '_display',
            'category',
            'id',
            'has_attachments',
            'location',
            'status',
            'reporter',
            'priority',
            'notes',
            'source',
            'text',
            'text_extra',
            'extra_properties',
            'created_at',
            'updated_at',
            'incident_date_start',
            'incident_date_end',
        )
        read_only_fields = (
            'id',
            'has_attachments',
        )

    def get_has_attachments(self, obj):
        return obj.attachments.exists()

    def update(self, instance, validated_data):
        """
        Perform update on nested models.

        Note:
        - Reporter cannot be updated via the API.
        - Atomic update (all fail/succeed), django signals on full success (see
          underlying update_multiple method of actions SignalManager).
        """
        user_email = self.context['request'].user.email

        for _property in [
                'location', 'status', 'category_assignment', 'priority'
        ]:
            if _property in validated_data:
                data = validated_data[_property]
                data['created_by'] = user_email

        if 'notes' in validated_data and validated_data['notes']:
            note_data = validated_data['notes'][0]
            note_data['created_by'] = user_email

        signal = Signal.actions.update_multiple(validated_data, instance)
        return signal