class DSSContainerSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for DSS Containers """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) envelopes = DSSEnvelopeSerializer( many=True, required=False, read_only=True, source='dss_envelopes', ) class Meta: model = DSSContainer fields = ( 'pk', 'name', 'path', 'read_write_setting', 'import_option', 'is_mounted', 'projects', 'envelopes', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'url', 'deleted', ) read_only_fields = ()
class ContactSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for Contacts """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = Contact fields = ('academic_title', 'first_name', 'last_name', 'email', 'phone', 'company', 'projects', 'notes', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'url', 'metadata', 'is_favourite') @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super(ContactSerializer, self).create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super(ContactSerializer, self).update(instance, validated_data)
class LabBookSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ Serializer for LabBook """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = LabBook fields = ( 'title', 'description', 'is_template', 'projects', 'url', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'metadata', 'is_favourite' ) @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super().create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super().update(instance, validated_data)
class LabbookSectionSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer): """ Serializer for LabbookSections """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) child_elements = LabBookChildElementPrimaryKeyRelatedField(many=True, required=False) class Meta: model = LabbookSection fields = ( 'title', 'date', 'projects', 'child_elements', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'url' )
class CommentSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ Serializer for Comments """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = Comment fields = ('content', 'projects', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'url', 'metadata', 'is_favourite') @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super(CommentSerializer, self).create(validated_data) self.create_metadata(metadata_list, instance) # read the request data and add the values of relates_to_content_type_id, relates_to_pk and private to the # instance so they can be used to create a relation within the viewset request = self.context['request'] instance.relates_to_content_type_id = request.data.get( 'relates_to_content_type_id') instance.relates_to_pk = request.data.get('relates_to_pk') instance.private = request.data.get('private') return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super(CommentSerializer, self).update(instance, validated_data)
class DriveSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for Drive """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) sub_directories = DirectorySerializer(many=True, required=False, read_only=True) # url for file-list sub_directories_url = HyperlinkedToListField( view_name="drive-sub_directories-list", lookup_field_name='drive_pk') dss_envelope_id = serializers.PrimaryKeyRelatedField( queryset=DSSEnvelope.objects.all(), source='envelope', many=False, required=False, allow_null=True) envelope_path = serializers.StringRelatedField(source='envelope', many=False, required=False, allow_null=True) container_id = serializers.PrimaryKeyRelatedField( queryset=DSSContainer.objects.all(), source='envelope.container', many=False, required=False, allow_null=True) webdav_url = serializers.SerializerMethodField() metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = Drive fields = ('title', 'projects', 'sub_directories', 'sub_directories_url', 'container_id', 'location', 'is_dss_drive', 'envelope_path', 'dss_envelope_id', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'url', 'webdav_url', 'metadata', 'imported', 'is_favourite') def get_webdav_url(self, obj): """ Returns the webdav URL for the current drive :param obj: :return: """ request = get_current_request() # strip all non-confirming characters from drive title pat = re.compile(r'[\W \-]+') stripped_title = re.sub(pat, ' ', obj.title) # temporarily remove webdav_url for DSS drives if not obj.is_dss_drive: return reverse('webdav-drive', request=request, kwargs={ 'drive': obj.pk, 'path': '/', 'drive_title': stripped_title }) return None @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super(DriveSerializer, self).create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super(DriveSerializer, self).update(instance, validated_data)
class KanbanBoardSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer): """ Serializer for Kanban Boards Includes Kanban Board Columns """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) kanban_board_columns = InternalKanbanBoardColumnSerializer(read_only=False, many=True, required=False) # provide a download link for the background image download_background_image = serializers.SerializerMethodField( read_only=True) # provide a download link for the background image download_background_image_thumbnail = serializers.SerializerMethodField( read_only=True) # write only for the background image background_image = serializers.FileField(write_only=True, required=False) class Meta: model = KanbanBoard fields = ('title', 'description', 'projects', 'kanban_board_columns', 'background_image', 'download_background_image', 'background_image_thumbnail', 'download_background_image_thumbnail', 'background_color', 'url', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'is_favourite') def get_download_background_image(self, obj): if not obj.background_image: return None request = get_current_request() path = reverse('kanbanboard-background-image.png', kwargs={'pk': obj.pk}) return build_expiring_jwt_url(request, path) def get_download_background_image_thumbnail(self, obj): if not obj.background_image_thumbnail: return None request = get_current_request() path = reverse('kanbanboard-background-image-thumbnail.png', kwargs={'pk': obj.pk}) return build_expiring_jwt_url(request, path) def create(self, validated_data): """ Override create method 0f KanbanBoardSerializer such that we can handle sub-serializers for - kanban_board_columns If a kanban board is created without columns, we add the default columns to the board :param validated_data: validated data of the serializer :return: KanbanBoard """ kanban_board_columns = None # get kanban board columns from validated data (if it is available) if 'kanban_board_columns' in validated_data: kanban_board_columns = validated_data.pop('kanban_board_columns') # create the kanban board using KanbanBoardSerializer (hence we needed to remove kanban_board_columns) instance = super(KanbanBoardSerializer, self).create(validated_data) # now create columns (if set) if kanban_board_columns: for item in kanban_board_columns: KanbanBoardColumn.objects.create(kanban_board=instance, title=item['title'], ordering=item['ordering'], color=item['color'], icon=item['icon']) else: # no kanban board columns set, create a default column "new" ordering = 0 KanbanBoardColumn.objects.create(ordering=0, title=_("New"), kanban_board=instance) # save the instance again so we trigger the changeset instance.save() return instance def update(self, instance, validated_data): """ Override update method of KanbanBoardSerializer such that we can handle sub-serializers for - kanban_board_columns :param instance: the instance that is being updated :param validated_data: validated data of the serializer :return: KanbanBoard """ kanban_board_columns = None # get kanban board columns from validated data (if it is available) if 'kanban_board_columns' in validated_data: kanban_board_columns = validated_data.pop('kanban_board_columns') # start a transaction which handles updating assigned_users, kanban_board_columns and updating the task itself with transaction.atomic(): # update kanban_board_columns if kanban_board_columns is not None: current_kanban_board_columns_pk = list( instance.kanban_board_columns.values_list('pk', flat=True)) for item in kanban_board_columns: if 'pk' in item: # update existing item real_item = KanbanBoardColumn.objects.filter( kanban_board=instance, pk=item['pk']).first() item_updated = False if item['title'] != real_item.title: real_item.title = item['title'] item_updated = True if item['color'] != real_item.color: real_item.color = item['color'] item_updated = True if item['ordering'] != real_item.ordering: real_item.ordering = item['ordering'] item_updated = True if item.get('icon', '') != real_item.icon: real_item.icon = item.get('icon', '') item_updated = True if item_updated: real_item.save() # remove pk from current_kanban_board_columns_pk try: current_kanban_board_columns_pk.remove(item['pk']) except ValueError as e: logger.error(e) else: # create new item KanbanBoardColumn.objects.create( kanban_board=instance, title=item['title'], icon=item.get('icon', ''), ordering=item['ordering'], color=item['color']) # finally, everything that is still in current_kanban_board_columns_pk needs to be removed KanbanBoardColumn.objects.filter( pk__in=current_kanban_board_columns_pk).delete() # update kanban board instance instance = super(KanbanBoardSerializer, self).update(instance, validated_data) return instance
class PictureSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for Picture """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) download_shapes = serializers.SerializerMethodField(read_only=True) download_background_image = serializers.SerializerMethodField( read_only=True) download_rendered_image = serializers.SerializerMethodField(read_only=True) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) def get_download_shapes(self, picture): return self.build_download_url(picture, 'picture-shapes-json') def get_download_background_image(self, picture): return self.build_download_url_with_token(picture, 'picture-background-image') def get_download_rendered_image(self, picture): if not picture.rendered_image: return self.get_download_background_image(picture) return self.build_download_url_with_token(picture, 'picture-rendered-image') @staticmethod def build_download_url_with_token(picture, reverse_url_name): request = get_current_request() if picture.uploaded_picture_entry is None: raise IntegrityError("uploaded_picture_entry is not referenced") path = reverse(reverse_url_name, kwargs={'pk': picture.uploaded_picture_entry.pk}) return build_expiring_jwt_url(request, path) @staticmethod def build_download_url(picture, reverse_url_name): request = get_current_request() path = reverse(reverse_url_name, kwargs={'pk': picture.uploaded_picture_entry.pk}) return request.build_absolute_uri(path) # all files should be write only - we have the download links as alternative # write only for the shapes shapes_image = serializers.FileField(write_only=True, required=False) # write only for the background image background_image = serializers.FileField(write_only=True, required=False) # write only for the rendered image rendered_image = serializers.FileField(write_only=True, required=False) class Meta: model = Picture fields = ('title', 'shapes_image', 'background_image', 'rendered_image', 'projects', 'width', 'height', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'download_shapes', 'download_background_image', 'download_rendered_image', 'url', 'metadata', 'is_favourite') @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super().create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super().update(instance, validated_data)
class FileSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for Files """ from eric.drives.models import Directory projects = ProjectPrimaryKeyRelatedField(many=True, required=False) download = serializers.SerializerMethodField(read_only=True) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) def get_download(self, file): request = get_current_request() path = reverse("file-download", kwargs={'pk': file.uploaded_file_entry.pk}) return request.build_absolute_uri(path) directory_id = serializers.PrimaryKeyRelatedField( # ToDo: We should also provide Directory.objects.viewable() here queryset=Directory.objects.all(), source='directory', many=False, required=False, allow_null=True) envelope_id = serializers.PrimaryKeyRelatedField( source='directory.drive.envelope', many=False, required=False, allow_null=True, read_only=True) container_id = serializers.PrimaryKeyRelatedField( source='directory.drive.envelope.container', many=False, required=False, allow_null=True, read_only=True) path = serializers.FileField(write_only=True, allow_empty_file=True) class Meta: model = File fields = ('title', 'name', 'description', 'path', 'original_filename', 'mime_type', 'projects', 'url', 'download', 'file_size', 'directory_id', 'envelope_id', 'container_id', 'is_dss_file', 'location', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'metadata', 'imported', 'is_favourite') read_only_fields = ('original_filename', 'mime_type', 'file_size', 'mime_type') @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super().create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) return super().update(instance, validated_data)
class TaskSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ REST API Serializer for Tasks """ assigned_users = PublicUserSerializer(read_only=True, many=True) assigned_users_pk = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), source='assigned_users', many=True, required=False) checklist_items = TaskCheckListItemSerializer(read_only=False, many=True, required=False) projects = ProjectPrimaryKeyRelatedField(many=True, required=False) labels = ElementLabelPrimaryKeyRelatedField(many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = Task fields = ( 'title', 'start_date', 'due_date', 'priority', 'state', 'description', 'projects', 'task_id', 'assigned_users', 'assigned_users_pk', 'checklist_items', 'labels', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'url', 'metadata', 'full_day', 'is_favourite', 'remind_assignees', 'reminder_datetime', ) read_only_fields = ('assigned_users', ) @transaction.atomic def create(self, validated_data): """ Override create method of TaskSerializer such that we can handle sub-serializers for - assigned_users - checklist_items - labels - metadata :param validated_data: validated data of the serializer :return: Task """ assigned_users = None checklist_items = None metadata_list = self.pop_metadata(validated_data) # get assigned users from validated data (if it is available) if 'assigned_users' in validated_data: assigned_users = validated_data.pop('assigned_users') # get checklist items from validated data (if it is available) if 'checklist_items' in validated_data: checklist_items = validated_data.pop('checklist_items') # create the task using TaskSerializer (hence we needed to remove assigned_users and checklist_items) instance = super(TaskSerializer, self).create(validated_data) # now create assigned users if assigned_users: for user in assigned_users: # instance.assigned_users.add(user) TaskAssignedUser.objects.create(task=instance, assigned_user=user) # and checklist items if checklist_items: for item in checklist_items: TaskCheckList.objects.create(task=instance, title=item['title'], checked=item['checked'], ordering=item['ordering']) self.create_metadata(metadata_list, instance) # save instance again so we can trigger the changeset instance.save() return instance @transaction.atomic def update(self, instance, validated_data): """ Override update method of TaskSerializer such that we can handle sub-serializers for - assigned_users - checklist_items :param instance: the instance that is being updated :param validated_data: validated data of the serializer :return: Task """ assigned_users = None checklist_items = None metadata_list = self.pop_metadata(validated_data) # get assigned users from validated data (if it is available) if 'assigned_users' in validated_data: assigned_users = validated_data.pop('assigned_users') # get checklist items from validated data (if it is available) if 'checklist_items' in validated_data: checklist_items = validated_data.pop('checklist_items') # update assigned_users if assigned_users is not None: currently_assigned_users = instance.assigned_users.all() users_to_remove = set(currently_assigned_users).difference( assigned_users) users_to_add = set(assigned_users).difference( set(currently_assigned_users)) # handle assigned users that need to be removed for user in users_to_remove: # The following is equal to: instance.assigned_users.remove(user) TaskAssignedUser.objects.filter(task=instance, assigned_user=user).delete() # handle assigned users that need to be added for user in users_to_add: # The following is equal to: instance.assigned_users.add(user) TaskAssignedUser.objects.create(task=instance, assigned_user=user) # update checklist_items if checklist_items is not None: current_checklist_items_pk = list( instance.checklist_items.values_list('pk', flat=True)) for item in checklist_items: if 'pk' in item: # update existing item real_item = TaskCheckList.objects.filter( task=instance, pk=item['pk']).first() real_item.checked = item['checked'] real_item.title = item['title'] real_item.ordering = item['ordering'] real_item.save() # remove pk from current_checklist_items_pk current_checklist_items_pk.remove(item['pk']) else: # create new item TaskCheckList.objects.create( task=instance, title=item['title'], checked=item['checked'], ordering=item['ordering'], ) # finally, everything that is still in current_checklist_items_pk needs to be removed TaskCheckList.objects.filter( pk__in=current_checklist_items_pk).delete() self.update_metadata(metadata_list, instance) # update task instance instance = super(TaskSerializer, self).update(instance, validated_data) return instance
class DmpSerializerExtended(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin): """ Serializer for DMPs """ dmp_form_title = serializers.SerializerMethodField(read_only=True) dmp_form_data = DmpFormDataSerializerExtended(many=True, required=False, read_only=True) projects = ProjectPrimaryKeyRelatedField(many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) class Meta: model = Dmp fields = ('url', 'title', 'status', 'dmp_form', 'dmp_form_title', 'dmp_form_data', 'projects', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'metadata', 'is_favourite') def get_dmp_form_title(self, dmp): try: return dmp.dmp_form.title except Dmp.dmp_form.RelatedObjectDoesNotExist: return None @transaction.atomic def create(self, validated_data): metadata_list = self.pop_metadata(validated_data) instance = super().create(validated_data) self.create_metadata(metadata_list, instance) return instance @transaction.atomic def update(self, instance, validated_data): metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) # list of changed dmp_form_data values initial_dmp_form_data = self.initial_data.get('dmp_form_data', []) # validated_data.pop('dmp_form_data') # iterate through list of changed dmp_form_data values for dmp_form_data in initial_dmp_form_data: # get pk and value from changed dmp_form_data data_pk = dmp_form_data.get('pk', None) data_value = dmp_form_data.get('value', None) # check if pk and value are set if data_pk and data_value is not None: # get the DB entry for dmp form data dmp_form_data_queryset = DmpFormData.objects.filter( dmp=instance, pk=data_pk) # check if an object with the pk exists if len(dmp_form_data_queryset) == 1: form_data_db_instance = dmp_form_data_queryset.first() # update form data value form_data_db_instance.value = data_value try: form_data_db_instance.save() except ValidationError as err: if 'value' in err.message_dict: raise ValidationError( {data_pk: err.message_dict['value']}) else: raise err else: raise NotFound else: raise ValidationError({ 'dmp_form_data': ValidationError(_('Not all fields are provided'), params={'assignment': self}, code='invalid') }) # last but not least, update DMP title and status # instance.title = validated_data.get('title', instance.title) # instance.status = validated_data.get('status', instance.status) # instance.project = validated_data.get('project', instance.project) # instance.dmp_form = validated_data.get('dmp_form', instance.dmp_form) # instance.save() return super(DmpSerializerExtended, self).update(instance, validated_data)
class MeetingSerializer(BaseModelWithCreatedByAndSoftDeleteSerializer, EntityMetadataSerializerMixin, ScheduledNotificationSerializer): """ Serializer for Meetings """ projects = ProjectPrimaryKeyRelatedField(many=True, required=False) resource = MinimalisticResourceSerializer(read_only=True) resource_pk = serializers.PrimaryKeyRelatedField( queryset=Resource.objects.all(), source='resource', many=False, required=False, allow_null=True) attending_users = PublicUserSerializer(read_only=True, many=True) attending_users_pk = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), source='attending_users', many=True, required=False) attending_contacts = MinimalisticContactSerializer(read_only=True, many=True) attending_contacts_pk = ContactPrimaryKeyRelatedField( source='attending_contacts', many=True, required=False) metadata = EntityMetadataSerializer( read_only=False, many=True, required=False, ) scheduled_notification = SerializerMethodField() def get_scheduled_notification(self, instance): return ScheduledNotificationSerializer.get_scheduled_notification( instance) # a separate named field is required for writing to ScheduledNotification scheduled_notification_writable = ScheduledNotificationSerializer( write_only=True, required=False) class Meta: model = Meeting fields = ('title', 'location', 'date_time_start', 'date_time_end', 'text', 'projects', 'url', 'resource', 'resource_pk', 'attending_users', 'attending_contacts', 'attending_contacts_pk', 'attending_users_pk', 'created_by', 'created_at', 'last_modified_by', 'last_modified_at', 'version_number', 'metadata', 'scheduled_notification', 'scheduled_notification_writable', 'full_day', 'is_favourite') @transaction.atomic def create(self, validated_data): """ Create a meeting with attending users and attending contacts :param validated_data: :return: """ attending_users = None attending_contacts = None scheduled_notification_writable = None if 'attending_users' in validated_data: attending_users = validated_data.pop('attending_users') if 'attending_contacts' in validated_data: attending_contacts = validated_data.pop('attending_contacts') if 'scheduled_notification_writable' in validated_data: scheduled_notification_writable = validated_data.pop( 'scheduled_notification_writable') metadata_list = self.pop_metadata(validated_data) # delegate creating the meeting to the current serializer instance = super(MeetingSerializer, self).create(validated_data) # read the request data and add the value of create_for to the instance, which is the pk of a user # in the MeetingViewSet we will use it to change attending_users accordingly and to give full access privilege request = self.context['request'] instance.create_for = request.data.get('create_for') # create attending users if attending_users: for user in attending_users: UserAttendsMeeting.objects.create(user=user, meeting=instance) # create attending contacts if attending_contacts: for contact in attending_contacts: ContactAttendsMeeting.objects.create(contact=contact, meeting=instance) self.create_metadata(metadata_list, instance) if scheduled_notification_writable: self.update_or_create_schedulednotification( scheduled_notification_writable, instance) return instance @transaction.atomic def update(self, instance, validated_data): """ Update a meeting with attending users and attending contacts :param instance: :param validated_data: :return: """ attending_contacts = None attending_users = None scheduled_notification_writable = None if 'attending_contacts' in validated_data: attending_contacts = validated_data.pop('attending_contacts') if 'attending_users' in validated_data: attending_users = validated_data.pop('attending_users') if 'scheduled_notification_writable' in validated_data: scheduled_notification_writable = validated_data.pop( 'scheduled_notification_writable') metadata_list = self.pop_metadata(validated_data) self.update_metadata(metadata_list, instance) if attending_users is not None: currently_attending_users = instance.attending_users.all() users_to_remove = set(currently_attending_users).difference( attending_users) users_to_add = set(attending_users).difference( set(currently_attending_users)) # handle attending users that need to be removed for user in users_to_remove: # The following is equal to: instance.attending_users.remove(user) UserAttendsMeeting.objects.filter(meeting=instance, user=user).delete() # handle attending users that need to be added for user in users_to_add: # The following is equal to: instance.attending_users.add(user) UserAttendsMeeting.objects.create(meeting=instance, user=user) if attending_contacts is not None: currently_attending_contacts = instance.attending_contacts.all() contacts_to_remove = set(currently_attending_contacts).difference( attending_contacts) contacts_to_add = set(attending_contacts).difference( set(currently_attending_contacts)) # handle attending contacts that need to be removed for contact in contacts_to_remove: # The following is equal to: instance.attending_contacts.remove(contact) ContactAttendsMeeting.objects.filter(meeting=instance, contact=contact).delete() # handle attending contacts that need to be added for contact in contacts_to_add: # The following is equal to: instance.attending_contacts.add(contact) ContactAttendsMeeting.objects.create(meeting=instance, contact=contact) instance = super(MeetingSerializer, self).update(instance, validated_data) if scheduled_notification_writable: self.update_or_create_schedulednotification( scheduled_notification_writable, instance) return instance