def update(self, request, force_request_data=None, *args, **kwargs): """ Updates an existing element. """ # force_request_data: # JSONField doesn't parse JSON data sent via API, therefore it can be necessary # to fake HTML input by passing a MultiValueDict as data to the serializer. # See also JSONField.get_value (.../rest_framework/fields.py) request_data = request.data if force_request_data is None else force_request_data # the following code is from MOdelViewSet.update partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request_data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} # temporarily disable revision model mixin for the response -> performance boost RevisionModelMixin.set_enabled(False) response = Response(serializer.data) RevisionModelMixin.set_enabled(True) return response
def migrate_idtags_out_of_existing_dir_metadata_json_forward( apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias dir_metadata_files = File.objects.using(db_alias).filter( name="dir_metadata.json", imported=True, ) with disable_permission_checks(File): for dir_metadata_file in dir_metadata_files: file_path = get_upload_to_path(dir_metadata_file, dir_metadata_file.name) file_path = os.path.join(dss_storage.location, file_path) if os.path.isfile(file_path): with open(file_path, 'r') as infile: dir_metadata_file_content = json.loads(infile.read()) for entry in dir_metadata_file_content: if "idtag" in entry.keys(): idtag_id = entry["idtag"] idtag = f"<p>idtag: {idtag_id}</p>" if idtag: File.objects.using(db_alias).filter( directory=dir_metadata_file.directory, imported=True, ).update(description=idtag) RevisionModelMixin.set_enabled(True)
def remove_calendaraccess_and_full_access(apps, schema_editor): """ Remove all entity permission assignments :param apps: :param schema_editor: :return: """ RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias CalendarAccess = apps.get_model('shared_elements', 'CalendarAccess') ContentType = apps.get_model('contenttypes', "ContentType") ModelPrivilege = apps.get_model('model_privileges', 'ModelPrivilege') # get content type calendaraccess_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='calendaraccess') with DisableSignals(): # remove all CalendarAccess objects CalendarAccess.objects.using(db_alias).all().delete() # remove all ModelPrivilege objects where the content_type is that of the CalendarAccess ModelPrivilege.objects.using(db_alias).all().filter( content_type=calendaraccess_content_type, ).delete() RevisionModelMixin.set_enabled(True)
def rename_auth_permission(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias Permission = apps.get_model("auth", "Permission") permissions = Permission.objects.using(db_alias).filter( codename__contains='_change_project') for permission in permissions: if permission.codename.endswith('_change_project'): modelname = permission.codename[0:permission.codename. index('_change_project')] new_perm_name = "%s_%s" % ('change_project', modelname) print("Renaming permission", permission.codename, "to", new_perm_name) permission.codename = new_perm_name try: permission.save() except: print('failed to rename, ignoring...') RevisionModelMixin.set_enabled(True)
def migrate_task_priority_choices_backward(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias # Task = apps.get_model('shared_elements', 'Task') with DisableSignals(): # avoid permission/lock checks Task.objects.using(db_alias).filter( priority=Task.TASK_PRIORITY_VERY_HIGH).update( priority=OLD_TASK_PRIORITY_VERY_HIGH) Task.objects.using(db_alias).filter( priority=Task.TASK_PRIORITY_HIGH).update( priority=OLD_TASK_PRIORITY_HIGH) Task.objects.using(db_alias).filter( priority=Task.TASK_PRIORITY_NORMAL).update( priority=OLD_TASK_PRIORITY_NORMAL) Task.objects.using(db_alias).filter( priority=Task.TASK_PRIORITY_LOW).update( priority=OLD_TASK_PRIORITY_LOW) Task.objects.using(db_alias).filter( priority=Task.TASK_PRIORITY_VERY_LOW).update( priority=OLD_TASK_PRIORITY_VERY_LOW) RevisionModelMixin.set_enabled(True)
def create_virtual_root_directory(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias Directory = apps.get_model("drives", "Directory") Drive = apps.get_model("drives", "Drive") with disable_permission_checks(Directory): with disable_permission_checks(Drive): # iterate over all drives for drive in Drive.objects.all(): # get all root directories of this drive root_directories = Directory.objects.filter(drive=drive, directory=None) if len(root_directories) == 1: # if there is only one root directory, we dedicate this as the new virtual root root_directories.update(is_virtual_root=True, name="/") else: # create a new virtual root and move all the others root_directory = Directory.objects.create( drive=drive, directory=None, is_virtual_root=True, name="/") root_directories.exclude(pk=root_directory.pk).update( directory=root_directory) RevisionModelMixin.set_enabled(True)
def fix_changeset_for_file_entries(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ChangeSet = apps.get_model('django_changeset', "ChangeSet") ContentType = apps.get_model('contenttypes', "ContentType") User = apps.get_model('auth', 'User') File = apps.get_model('projects', 'File') UploadedFileEntry = apps.get_model('projects', 'UploadedFileEntry') # get the first superuser as the alternative user alternative_user = User.objects.filter(is_superuser=True).first() file_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='file') uploaded_file_entry_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='uploadedfileentry') # iterate over all file entries for entry in UploadedFileEntry.objects.using(db_alias).all(): # check if the file entry has an insert changeset insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=entry.pk, object_type=uploaded_file_entry_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: # get insert_changest of file file_insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=entry.file.pk, object_type=file_content_type, changeset_type='I').first() # also, if there is no insert changeset, we need to create one if not file_insert_changeset: file_insert_changeset = ChangeSet.objects.using( db_alias).create(object_uuid=entry.file.pk, object_type=file_content_type, changeset_type='I', user=alternative_user) # get user of file_insert_changeset file_created_by = file_insert_changeset.user # create a new insert changeset for the file entry insert_changeset = ChangeSet.objects.using(db_alias).create( user=file_created_by, object_uuid=entry.pk, object_type=uploaded_file_entry_content_type, changeset_type='I') RevisionModelMixin.set_enabled(True)
def fix_privileges_up(apps, schema_editor): RevisionModelMixin.set_enabled(False) with disable_permission_checks(ModelPrivilege): privileges = ModelPrivilege.objects.all() for privilege in privileges: fix_privileges(privilege) RevisionModelMixin.set_enabled(True)
def change_project_state_from_deleted_to_cancel(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias Project = apps.get_model('projects', 'Project') Project.objects.filter(project_state=DELETED).update(project_state=CANCEL, deleted=True) RevisionModelMixin.set_enabled(True)
def fill_rolepermissionassignment_uuid(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias RolePermissionAssignment = apps.get_model('projects', 'rolepermissionassignment') for obj in RolePermissionAssignment.objects.using(db_alias).all(): obj.uuid = uuid.uuid4() obj.save() RevisionModelMixin.set_enabled(True)
def convert_task_state_from_new_to_old(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias Task = apps.get_model("shared_elements", "Task") with disable_permission_checks(Task): for task in Task.objects.all(): task.state = TASK_STATE_MAPPING_NEW_TO_OLD[task.state] task.save() RevisionModelMixin.set_enabled(True)
def clear_entity_permission_assignment(apps, schema_editor): """ Remove all entity permission assignments :param apps: :param schema_editor: :return: """ RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ModelPrivilege = apps.get_model('projects', 'ModelPrivilege') ModelPrivilege.objects.all().delete() RevisionModelMixin.set_enabled(True)
def migrate_resource_usage_settings_fields_backward(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias # Resource = apps.get_model('shared_elements', 'Resource') with DisableSignals(): # avoid permission/lock checks Resource.objects.using(db_alias).filter( general_usage_setting=Resource.SELECTED_GROUPS).update( general_usage_setting=OLD_SELECTED_GROUPS) Resource.objects.using(db_alias).filter( general_usage_setting=Resource.GLOBAL).update( general_usage_setting=OLD_GLOBAL) RevisionModelMixin.set_enabled(True)
def create_entity_permission_assignment(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ChangeSet = apps.get_model('django_changeset', "ChangeSet") ContentType = apps.get_model('contenttypes', "ContentType") User = apps.get_model('auth', 'User') Dmp = apps.get_model('dmp', 'dmp') ModelPrivilege = apps.get_model('model_privileges', 'ModelPrivilege') # get the first superuser as the alternative user alternative_user = User.objects.filter(is_superuser=True).first() # get all content types dmp_content_type = ContentType.objects.using(db_alias).get(app_label='dmp', model='dmp') # iterate over all tasks and create an entity permission assignment for dmp in Dmp.objects.using(db_alias).all(): # get the insert changeset of this object insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=dmp.pk, object_type=dmp_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=dmp.pk, object_type=dmp_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "DMP with ID {id} does not have created_by, assuming user={user}" .format(id=dmp.id, user=alternative_user)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=dmp_content_type, object_id=dmp.id, is_owner=True) RevisionModelMixin.set_enabled(True)
def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list)) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) # temporarily disable revision model mixin for the response -> performance boost RevisionModelMixin.set_enabled(False) # return only the count of added paths here for performance count = len(serializer.data) data = {'count': count} response = Response(data, status=status.HTTP_201_CREATED, headers=headers) RevisionModelMixin.set_enabled(True) return response
def clear_entity_permission_assignment(apps, schema_editor): """ Remove all entity permission assignments :param apps: :param schema_editor: :return: """ RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ContentType = apps.get_model('contenttypes', "ContentType") # get all content types dmp_content_type = ContentType.objects.using(db_alias).get(app_label='dmp', model='dmp') ModelPrivilege = apps.get_model('model_privileges', 'ModelPrivilege') ModelPrivilege.objects.filter(content_type=dmp_content_type).delete() RevisionModelMixin.set_enabled(True)
def create(self, request, force_request_data=None, *args, **kwargs): """ Creates a new element. """ # force_request_data: # JSONField doesn't parse JSON data sent via API, therefore it can be necessary # to fake HTML input by passing a MultiValueDict as data to the serializer. # See also JSONField.get_value (.../rest_framework/fields.py) request_data = request.data if force_request_data is None else force_request_data # the following code is from ModelViewSet.create serializer = self.get_serializer(data=request_data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) # temporarily disable revision model mixin for the response -> performance boost RevisionModelMixin.set_enabled(False) response = Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) RevisionModelMixin.set_enabled(True) return response
def create_calendaraccess_and_full_access(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ContentType = apps.get_model('contenttypes', "ContentType") User = apps.get_model('auth', 'User') CalendarAccess = apps.get_model('shared_elements', 'CalendarAccess') ModelPrivilege = apps.get_model('model_privileges', 'ModelPrivilege') # get content type calendaraccess_content_type = ContentType.objects.get_for_model( CalendarAccess) # iterate over all Users, create a CalendarAccess and give the user full_access # If the user never logged in we don't do anything here. # There is still a handler that does the same on authentication for "new" users. with DisableSignals(): # avoid permission/lock checks for user in User.objects.using(db_alias).filter( last_login__isnull=False): # check if a CalendarAccess already exists for this user privilege_exists = CalendarAccess.objects.using( db_alias).all().filter(created_by=user, ) if not privilege_exists: new_privilege = CalendarAccess.objects.using(db_alias).create() # set created_by and last_modified_by to the user, so the CalendarAccess is his/hers new_privilege.created_by = user new_privilege.last_modified_by = user new_privilege.save() # now give the user full_access to his calendar perm = ModelPrivilege(user=user, full_access_privilege="AL", content_type=calendaraccess_content_type, object_id=new_privilege.pk) perm.save() RevisionModelMixin.set_enabled(True)
def remove_virtual_root_directory(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias Directory = apps.get_model("drives", "Directory") Drive = apps.get_model("drives", "Drive") with disable_permission_checks(Directory): with disable_permission_checks(Drive): # iterate over all drives for drive in Drive.objects.all(): # get all virtual root directories of this drive virtual_root_directories = Directory.objects.filter( drive=drive, directory=None, is_virtual_root=True) # get all directories that have one of those virtual root directories as parent sub_root_directories = Directory.objects.filter( directory__in=virtual_root_directories) # update their parents sub_root_directories.update(directory=None) # and delete virtual root directories virtual_root_directories.delete() RevisionModelMixin.set_enabled(True)
def create_entity_permission_assignment(apps, schema_editor): RevisionModelMixin.set_enabled(False) db_alias = schema_editor.connection.alias ChangeSet = apps.get_model('django_changeset', "ChangeSet") ContentType = apps.get_model('contenttypes', "ContentType") User = apps.get_model('auth', 'User') Task = apps.get_model('projects', 'Task') Note = apps.get_model('projects', 'Note') Meeting = apps.get_model('projects', 'Meeting') Contact = apps.get_model('projects', 'Contact') File = apps.get_model('projects', 'File') # get the first superuser as the alternative user alternative_user = User.objects.filter(is_superuser=True).first() ModelPrivilege = apps.get_model('projects', 'ModelPrivilege') # get all content types task_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='task') note_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='note') meeting_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='meeting') contact_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='contact') file_content_type = ContentType.objects.using(db_alias).get( app_label='shared_elements', model='file') # iterate over all tasks and create an entity permission assignment for task in Task.objects.using(db_alias).all(): # get the insert changeset of this object insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=task.pk, object_type=task_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=task.pk, object_type=task_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "Task with ID {id} does not have created_by, assuming user={user}" .format(id=task.id, user=alternative_user)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=task_content_type, object_id=task.id, is_owner=True) # iterate over all notes and create an entity permission assignment for note in Note.objects.using(db_alias).all(): insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=note.pk, object_type=note_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=note.pk, object_type=note_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "Note with ID {id} does not have created_by, assuming user={user}" .format(id=note.id, user=alternative_user.username)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=note_content_type, object_id=note.id, is_owner=True) # iterate over all meetings and create an entity permission assignment for meeting in Meeting.objects.using(db_alias).all(): insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=meeting.pk, object_type=meeting_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=meeting.pk, object_type=meeting_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "Meeting with ID {id} does not have created_by, assuming user={user}" .format(id=meeting.id, user=alternative_user.username)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=meeting_content_type, object_id=meeting.id, is_owner=True) # iterate over all contacts and create an entity permission assignment for contact in Contact.objects.using(db_alias).all(): insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=contact.pk, object_type=contact_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=contact.pk, object_type=contact_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "Contact with ID {id} does not have created_by, assuming user={user}" .format(id=contact.id, user=alternative_user.username)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=contact_content_type, object_id=contact.id, is_owner=True) # iterate over all files and create an entity permission assignment for file in File.objects.using(db_alias).all(): insert_changeset = ChangeSet.objects.using(db_alias).filter( object_uuid=file.pk, object_type=file_content_type, changeset_type='I').first() # if there is no insert changeset, create one if not insert_changeset: insert_changeset = ChangeSet.objects.using(db_alias).create( user=alternative_user, object_uuid=file.pk, object_type=file_content_type, changeset_type='I') created_by = insert_changeset.user if not created_by: print( "File with ID {id} does not have created_by, assuming user={user}" .format(id=file.id, user=alternative_user.username)) created_by = alternative_user ModelPrivilege.objects.create(user_id=created_by.id, content_type=file_content_type, object_id=file.id, is_owner=True) RevisionModelMixin.set_enabled(True)
def update_all(self, *args, **kwargs): """ Change positioning, width, height, and order of labbook child elements """ # verify that we are getting an array of data assert isinstance(self.request.data, list), "Expected array" request_pk_list = [] # loop over request.data and collect PKs of child element for child_element in self.request.data: if 'pk' in child_element: request_pk_list.append(child_element['pk']) else: # we do not support creating a new element here raise ValidationError( _("Element with no primary key supplied, but this endpoint does not support creating new elements" )) # query all editable LabBookChildElement with all primary keys from the request child_elements = LabBookChildElement.objects.filter( lab_book=self.parent_object, pk__in=request_pk_list).editable() # fetch them and put them into a dictionary child_elements = { str(obj._get_pk_val()): obj for obj in child_elements } # make sure the elements have the same length (else something is wrong with the pk, assignment or permissions) if len(child_elements) != len(request_pk_list): raise ValidationError( _("Invalid primary key suplied - element does not belong to the labbook" )) # the remainder of this can be done without permission checks (we already know the element is editable) with disable_permission_checks(LabBookChildElement): for child_element in self.request.data: pk = child_element['pk'] # get item from existing child_elements real_item = child_elements[pk] # check if anything has changed -> only fire a database query if the element really needs to be changed if real_item.position_x != child_element['position_x'] \ or real_item.position_y != child_element['position_y'] \ or real_item.width != child_element['width'] \ or real_item.height != child_element['height']: # something has changed -> update it in database real_item.position_x = child_element['position_x'] real_item.position_y = child_element['position_y'] real_item.width = child_element['width'] real_item.height = child_element['height'] real_item.save() # call save method of parent object, such that the Django ChangeSet stores the changes on the "related_many" # self.parent_object.save() # commented out on purpose - this causes an issue with moving elements - see ticket-259775 # temporarily disable revision model to improve performance for providing the list endpoint here RevisionModelMixin.set_enabled(False) # ToDo: It is not necessary to return the whole list of elements here (needs frontend adaptation too) response = Response(request_pk_list) RevisionModelMixin.set_enabled(True) return response
def move_assignment(self, *args, **kwargs): """ Moves a task from one column to another, also updates the ordering if necessary. """ from timeit import default_timer as timer start = timer() to_column = self.request.data['to_column'] to_index = self.request.data['to_index'] assignment_pk = self.request.data['assignment_pk'] # Check if to_column is in the same kanban board (if not, this will raise an exception) real_column = self.parent_object.kanban_board_columns.filter( pk=to_column).first() if not real_column: raise NotFound # get the assignment that we need to change (no need to call viewable here; if the user is allowed to access # the parent object, the user is also allowed to access the column) assignment = KanbanBoardColumnTaskAssignment.objects.filter( pk=assignment_pk).select_related('task').first() if not assignment: raise NotFound # store current ordering (this is the index where the assignment is moved FROM) from_index = assignment.ordering # store current column of assignment from_column = str(assignment.kanban_board_column_id) if from_index == to_index and from_column == to_column: # no changes return self.list(self.request, *args, **kwargs) if from_column != to_column: # easy: if from_column != to_column, we just need to make space in to_column and fill the gap in from_column # step 1: make space in the to_column: Shift everything in the to_column that is below to_index down by 1 task_assignments = KanbanBoardColumnTaskAssignment.objects.filter( kanban_board_column_id=to_column, ordering__gte=to_index).exclude(pk=assignment_pk) task_assignments.update(ordering=F('ordering') + 1) # step 2: now that we have made some room, we can set the ordering of our assignment assignment.ordering = to_index assignment.kanban_board_column_id = to_column assignment.save() # step 3: now that the original element was removed, close the gap in from_column by decreasing ordering # of the assignments in from_column task_assignments = KanbanBoardColumnTaskAssignment.objects.filter( kanban_board_column_id=from_column, ordering__gte=from_index).exclude(pk=assignment_pk) task_assignments.update(ordering=F('ordering') - 1) else: # from_column == to_column # move within the same column logger.debug( "KanbanBoardColumnTaskAssignmentViewSet.move_assignment(board_id={}): Moving assignment " "within the same column ".format(self.parent_object.pk)) ######################################################################################################## # step 1: make space in the to_column: Shift everything in the to_column after to_index down by 1 ######################################################################################################## task_assignments = KanbanBoardColumnTaskAssignment.objects.viewable( ).filter(kanban_board_column_id=to_column).exclude( pk=assignment_pk) if from_index < to_index: # as we are moving down, we need to decrease to_index - else we move it too far to_index -= 1 # move down within the same column task_assignments = task_assignments.filter( ordering__gte=from_index, ordering__lte=to_index) # just mass-decrease the ordering by 1 task_assignments.update(ordering=F('ordering') - 1) else: # move up within the same column task_assignments = task_assignments.filter( ordering__gte=to_index, ordering__lte=from_index, ) # just mass-increase the ordering by 1 task_assignments.update(ordering=F('ordering') + 1) ######################################################################################################## # step 2: now that we have made some room, we can set the ordering of the current assignment without # causing a DB/consistency conflict (uniqueness of ordering + kanban_board_column) ######################################################################################################## assignment.ordering = to_index assignment.kanban_board_column_id = to_column assignment.save() ######################################################################################################## # step 3: Consistency check - make sure the from_column has a consistent ordering again # in an optimal world, this should not never happen - so worst case it's just a READ QUERY, and # not an UPDATE QUERY ######################################################################################################## task_assignments = KanbanBoardColumnTaskAssignment.objects.viewable( ).filter(kanban_board_column_id=from_column).order_by('ordering') # iterate over all task assignments of the current column and fix ordering (in case it needs to be fixed) i = 0 with disable_permission_checks(KanbanBoardColumnTaskAssignment): for a in task_assignments: # for this assignment we expect the ordering to be i if a.ordering != i: # wrong ordering detected, fix it logger.debug( "KanbanBoardColumnTaskAssignmentViewSet.move_assignment(): " "Detected ordering of assignment is {}, but expected it to be {}" .format(a.ordering, i)) a.ordering = i a.save(update_fields=['ordering']) i = i + 1 end = timer() logger.debug( "KanbanBoardColumnTaskAssignmentViewSet.move_assignment(board_id={}): " "Moving took {} seconds".format(self.parent_object.pk, (end - start))) # return the QUERY/LIST api call from django_changeset.models import RevisionModelMixin # temporarily disable revision model mixin for the list api call RevisionModelMixin.set_enabled(False) response = self.list(self.request, *args, **kwargs) RevisionModelMixin.set_enabled(True) return response
def create_many(self, *args, **kwargs): """ Assigns multiple tasks to a board. """ # verify that we are getting an array of data assert isinstance(self.request.data, list), "Expected array" # collect task_ids and column_ids of assignment task_ids = [] column_ids = [] for assignment in self.request.data: task_ids.append(assignment['task_id']) column_ids.append(assignment['kanban_board_column']) # the task ids should be unique, lets verify that we actually have access on those tasks if Task.objects.viewable().filter( pk__in=task_ids).count() != len(task_ids): # the number of tasks in task_ids differs from the number of tasks that the current user is allowed to # view raise NotFound # since Django 1.11, there is a weird behaviour of QueryDicts that are immutable if isinstance( self.request.data, QueryDict ): # however, some request.data objects are normal dictionaries... self.request.data._mutable = True # for each column we need to determine the currently highest ordering, and the expected next ordering max_ordering_qs = KanbanBoardColumnTaskAssignment.objects.filter( kanban_board_column__kanban_board=self.parent_object).order_by( 'kanban_board_column').values('kanban_board_column').annotate( max_ordering=Max('ordering', output_field=FloatField())) # convert max_ordering_qs into a dict, where the kanban_board_column is the primary key and the index field max_ordering = {} for result in max_ordering_qs: max_ordering[str( result['kanban_board_column'])] = result['max_ordering'] # now fill in the max ordering for assignment in self.request.data: column_id = assignment['kanban_board_column'] if column_id not in max_ordering: # column_id not found in our max_ordering dict, setting value to 0 max_ordering[column_id] = 0 else: # found! increase by 1 for the new assignment max_ordering[column_id] += 1 assignment['ordering'] = max_ordering[column_id] serializer = self.get_serializer(data=self.request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) # return the QUERY/LIST api call from django_changeset.models import RevisionModelMixin # temporarily disable revision model mixin for the list api call RevisionModelMixin.set_enabled(False) response = self.list(self.request, *args, **kwargs) RevisionModelMixin.set_enabled(True) return response