def load_ws_data(self): workflow_data = { **self._load_workflow_ws_data(), "tab_slugs": list(self.workflow.live_tabs.values_list("slug", flat=True)), } if self.tab.is_deleted: wfm_ids = list( # tab.live_wf_modules can be nonempty even when tab.is_deleted self.tab.live_wf_modules.values_list("id", flat=True)) return { "updateWorkflow": workflow_data, "clearTabSlugs": [self.tab.slug], "clearWfModuleIds": [str(wfm_id) for wfm_id in wfm_ids], } else: return { "updateWorkflow": workflow_data, "updateTabs": { self.tab.slug: TabSerializer(self.tab).data }, "updateWfModules": { str(wfm.id): WfModuleSerializer(wfm).data for wfm in self.tab.live_wf_modules }, }
def load_ws_data(self): data = { 'updateWorkflow': self._load_workflow_ws_data(), 'updateWfModules': { str(wfm_id): { 'last_relevant_delta_id': delta_id, 'quick_fixes': [], 'output_columns': [], 'output_error': '', 'output_status': 'busy', 'output_n_rows': 0, } for wfm_id, delta_id in self._changed_wf_module_versions } } if hasattr(self, 'wf_module'): if self.wf_module.is_deleted or self.wf_module.tab.is_deleted: # When we did or undid this command, we removed the # WfModule from the Workflow. data['clearWfModuleIds'] = [self.wf_module_id] else: # Serialize _everything_, including params # # TODO consider serializing only what's changed, so when Alice # changes 'has_header' it doesn't overwrite Bob's 'url' while # he's editing it. step_data = WfModuleSerializer(self.wf_module).data data['updateWfModules'][str(self.wf_module_id)] = step_data return data
def workflow_addmodule(request, pk, format=None): workflow = get_object_or_404(Workflow, pk=pk) if not workflow.user_authorized_write(request.user): return HttpResponseForbidden() module_id = request.data['moduleId'] insert_before = int(request.data['insertBefore']) try: module = Module.objects.get(pk=module_id) except Module.DoesNotExist: return Response(status=status.HTTP_400_BAD_REQUEST) # always add the latest version of a module (do we need ordering on the objects to ensure last is always latest?) module_version = ModuleVersion.objects.filter(module=module).last() log_user_event(request.user, 'Add Module', {'name': module.name, 'id_name':module.id_name}) watch_list = ['columnchart', 'linechart', 'loadurl', 'twitter', 'googlesheets'] if module.id_name in watch_list: log_user_event(request.user, 'Add ' + module.name, {'name': module.name, 'id_name':module.id_name}) with workflow.cooperative_lock(): delta = AddModuleCommand.create(workflow, module_version, insert_before) serializer = WfModuleSerializer(delta.wf_module) wfmodule_data = serializer.data wfmodule_data['insert_before'] = request.data['insertBefore'] return Response(wfmodule_data, status.HTTP_201_CREATED)
def embed(request, wfmodule_id): try: wf_module = WfModule.objects.get(pk=wfmodule_id, is_deleted=False) except WfModule.DoesNotExist: wf_module = None if wf_module and (not wf_module.workflow or not wf_module.workflow.request_authorized_read(request) or not wf_module.module_version or not wf_module.module_version.html_output): wf_module = None if wf_module: workflow_module_serializer = WfModuleSerializer(wf_module) workflow_serializer = WorkflowSerializerLite( wf_module.workflow, context={"request": request}) init_state = { "workflow": workflow_serializer.data, "wf_module": workflow_module_serializer.data, } else: init_state = {"workflow": None, "wf_module": None} response = TemplateResponse(request, "embed.html", {"initState": init_state}) return response
def workflow_addmodule(request, pk, format=None): workflow = get_object_or_404(Workflow, pk=pk) if not workflow.request_authorized_write(request): return HttpResponseForbidden() module_id = int(request.data['moduleId']) index = int(request.data['index']) try: module = Module.objects.get(pk=module_id) except Module.DoesNotExist: return Response(status=status.HTTP_400_BAD_REQUEST) # don't allow python code module in anonymous workflow if module.id_name == 'pythoncode' and workflow.is_anonymous: return HttpResponseForbidden() # always add the latest version of a module (do we need ordering on the # objects to ensure last is always latest?) module_version = ModuleVersion.objects.filter(module=module).last() server.utils.log_user_event(request, 'Add Module ' + module.name, { 'name': module.name, 'id_name': module.id_name }) delta = AddModuleCommand.create(workflow, module_version, index) serializer = WfModuleSerializer(delta.wf_module) wfmodule_data = serializer.data return Response({ 'wfModule': wfmodule_data, 'index': index, }, status.HTTP_201_CREATED)
def load_ws_data(self): workflow_data = { **self._load_workflow_ws_data(), 'tab_slugs': list(self.workflow.live_tabs.values_list('slug', flat=True)) } if self.tab.is_deleted: wfm_ids = list( # tab.live_wf_modules can be nonempty even when tab.is_deleted self.tab.live_wf_modules.values_list('id', flat=True)) return { 'updateWorkflow': workflow_data, 'clearTabSlugs': [self.tab.slug], 'clearWfModuleIds': [str(wfm_id) for wfm_id in wfm_ids], } else: return { 'updateWorkflow': workflow_data, 'updateTabs': { self.tab.slug: TabSerializer(self.tab).data }, 'updateWfModules': { str(wfm.id): WfModuleSerializer(wfm).data for wfm in self.tab.live_wf_modules }, }
def get_workflow_as_delta_and_needs_render(self): """ Return (apply-delta dict, needs_render), or raise Workflow.DoesNotExist needs_render is a (workflow_id, delta_id) pair. """ with Workflow.authorized_lookup_and_cooperative_lock( 'read', self.scope['user'], self.scope['session'], pk=self.workflow_id) as workflow: request = RequestWrapper(self.scope['user'], self.scope['session']) ret = { 'updateWorkflow': (WorkflowSerializer(workflow, context={ 'request': request }).data), } tabs = list(workflow.live_tabs) ret['updateTabs'] = dict( (tab.slug, TabSerializer(tab).data) for tab in tabs) wf_modules = list(WfModule.live_in_workflow(workflow.id)) ret['updateWfModules'] = dict( (str(wfm.id), WfModuleSerializer(wfm).data) for wfm in wf_modules) if workflow.are_all_render_results_fresh(): needs_render = None else: needs_render = (workflow.id, workflow.last_delta_id) return (ret, needs_render)
def load_ws_data(self): data = { "updateWorkflow": self._load_workflow_ws_data(), "updateWfModules": { str(wfm_id): { "last_relevant_delta_id": delta_id, "quick_fixes": [], "output_columns": [], "output_error": "", "output_status": "busy", "output_n_rows": 0, } for wfm_id, delta_id in self._changed_wf_module_versions }, } if hasattr(self, "wf_module"): if self.wf_module.is_deleted or self.wf_module.tab.is_deleted: # When we did or undid this command, we removed the # WfModule from the Workflow. data["clearWfModuleIds"] = [self.wf_module_id] else: # Serialize _everything_, including params # # TODO consider serializing only what's changed, so when Alice # changes 'has_header' it doesn't overwrite Bob's 'url' while # he's editing it. step_data = WfModuleSerializer(self.wf_module).data data["updateWfModules"][str(self.wf_module_id)] = step_data return data
def make_init_state(request, workflow=None, modules=None): """Build a dict to embed as JSON in `window.initState` in HTML.""" ret = {} if workflow: ret['workflowId'] = workflow.id ret['workflow'] = WorkflowSerializer(workflow, context={'request': request}).data wf_modules = workflow.wf_modules \ .prefetch_related('parameter_vals__parameter_spec', 'module_version') wf_module_data_list = WfModuleSerializer(wf_modules, many=True).data ret['wfModules'] = dict([(str(wfm['id']), wfm) for wfm in wf_module_data_list]) ret['selected_wf_module'] = workflow.selected_wf_module ret['uploadConfig'] = { 'bucket': minio.UserFilesBucket, 'accessKey': settings.MINIO_ACCESS_KEY, # never _SECRET_KEY 'server': settings.MINIO_EXTERNAL_URL } ret['user_files_bucket'] = minio.UserFilesBucket del ret['workflow']['selected_wf_module'] if modules: modules_data_list = ModuleSerializer(modules, many=True).data ret['modules'] = dict([(str(m['id']), m) for m in modules_data_list]) if request.user.is_authenticated(): ret['loggedInUser'] = UserSerializer(request.user).data if workflow and not workflow.request_read_only(request): ret['updateTableModuleIds'] = load_update_table_module_ids() return ret
def make_init_state(request, workflow=None, modules=None): """Build a dict to embed as JSON in `window.initState` in HTML.""" ret = {} if workflow: ret['workflowId'] = workflow.id ret['workflow'] = WorkflowSerializer(workflow, context={'request': request}).data wf_modules = workflow.wf_modules \ .prefetch_related('parameter_vals__parameter_spec', 'module_version') wf_module_data_list = WfModuleSerializer(wf_modules, many=True).data ret['wfModules'] = dict([(str(wfm['id']), wfm) for wfm in wf_module_data_list]) ret['selected_wf_module'] = workflow.selected_wf_module del ret['workflow']['selected_wf_module'] if modules: modules_data_list = ModuleSerializer(modules, many=True).data ret['modules'] = dict([(str(m['id']), m) for m in modules_data_list]) if request.user.is_authenticated(): ret['loggedInUser'] = UserSerializer(request.user).data if workflow and not workflow.request_read_only(request): ret['updateTableModuleIds'] = load_update_table_module_ids() return ret
def load_ws_data(self): """ Notify WebSocket clients that we just undid or redid. This default implementation sends a 'delta' command. It will always include a 'set-workflow' property; it may include a 'set-wf-module' command and may include a 'clear-wf-module' command. This must be called within the same Workflow.cooperative_lock() that triggered the change in the first place. """ workflow = self.workflow data = { 'updateWorkflow': { 'name': workflow.name, 'revision': workflow.revision(), 'wf_modules': list(workflow.wf_modules.values_list('id', flat=True)), 'public': workflow.public, 'last_update': workflow.last_update().isoformat(), }, 'updateWfModules': {} } if hasattr(self, '_changed_wf_module_versions'): for id, delta_id in self._changed_wf_module_versions.items(): data['updateWfModules'][str(id)] = { 'last_relevant_delta_id': delta_id, 'error_msg': '', 'status': 'busy', 'quick_fixes': [], 'output_columns': None, 'output_n_rows': None, 'cached_render_result_id': None, } if hasattr(self, 'wf_module'): self.wf_module.refresh_from_db() if self.wf_module.workflow_id: # Serialize _everything_, including params # # TODO consider serializing only what's changed, so when Alice # changes 'has_header' it doesn't overwrite Bob's 'url' while # he's editing it. wf_module_data = WfModuleSerializer(self.wf_module).data data['updateWfModules'][str(self.wf_module_id)] = \ _prepare_json(wf_module_data) else: # When we did or undid this command, we removed the # WfModule from the Workflow. data['clearWfModuleIds'] = [self.wf_module_id] return data
async def ws_notify(self): """ Notify WebSocket clients that we just undid or redid. This default implementation sends a 'delta' command. It will always include a 'set-workflow' property; it may include a 'set-wf-module' command and may include a 'clear-wf-module' command. """ data = {} with self.workflow.cooperative_lock(): workflow_data = WorkflowSerializer(self.workflow).data # Remove the data we didn't generate correctly because we had no # HTTP request. del workflow_data['is_anonymous'] del workflow_data['owner_name'] del workflow_data['read_only'] data['updateWorkflow'] = _prepare_json(workflow_data) data['updateWfModules'] = {} if hasattr(self, '_changed_wf_module_versions'): for id, delta_id in self._changed_wf_module_versions.items(): data['updateWfModules'][str(id)] = { 'last_relevant_delta_id': delta_id, 'error_msg': '', # Tell clients this WfModule is "busy". Don't actually # _set_ the busy flag: the busy flag is set exclusively # by "fetch" (`module_dispatch_event`) and we can't # override that safely. We don't need another busy flag # for renders: this transient status is enough. 'status': 'busy', 'quick_fixes': [], 'output_columns': None } if hasattr(self, 'wf_module'): self.wf_module.refresh_from_db() if self.wf_module.workflow_id: wf_module_data = WfModuleSerializer(self.wf_module).data wf_module_data['status'] = 'busy' # rationale above data['updateWfModules'][str(self.wf_module_id)] = \ _prepare_json(wf_module_data) else: # When we did or undid this command, we removed the # WfModule from the Workflow. data['clearWfModuleIds'] = [self.wf_module_id] await websockets.ws_client_send_delta_async(self.workflow_id, data)
def wfmodule_detail(request, pk, format=None): try: wf_module = WfModule.objects.get(pk=pk) except WfModule.DoesNotExist: return HttpResponseNotFound() if request.method in ['POST', 'DELETE', 'PATCH']: if not wf_module.user_authorized_write(request.user): return HttpResponseForbidden() if not wf_module.user_authorized_read(request.user): return HttpResponseNotFound() if request.method == 'GET': with wf_module.workflow.cooperative_lock(): serializer = WfModuleSerializer(wf_module) return Response(serializer.data) elif request.method == 'DELETE': delta = DeleteModuleCommand.create(wf_module) if delta: return Response(status=status.HTTP_204_NO_CONTENT) else: return HttpResponseNotFound() # missing wf_module; can happen if two DELETE requests race. elif request.method == 'PATCH': # For patch, we check which fields are set in data, and process all of them # TODO: replace all of these with the generic patch method, most of this is unnecessary try: if not set(request.data.keys()).intersection({"notes", "auto_update_data", "collapsed", "notifications"}): raise ValueError('Unknown fields: {}'.format(request.data)) with wf_module.workflow.cooperative_lock(): if 'notes' in request.data: patch_notes(wf_module, request.data) if 'auto_update_data' in request.data: patch_update_settings(wf_module, request.data) if 'collapsed' in request.data: wf_module.set_is_collapsed(request.data['collapsed'], notify=False) if 'notifications' in request.data: patch_wfmodule(wf_module, request.data) except Exception as e: return Response({'message': str(e), 'status_code': 400}, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT)
def wfmodule_detail(request, pk, format=None): try: wf_module = WfModule.objects.get(pk=pk) except WfModule.DoesNotExist: return HttpResponseNotFound() if not wf_module.user_authorized(request.user): return HttpResponseForbidden() if request.method == 'GET': serializer = WfModuleSerializer(wf_module) return Response(serializer.data) elif request.method == 'DELETE': DeleteModuleCommand.create(wf_module) return Response(status=status.HTTP_204_NO_CONTENT)
def wfmodule_detail(request, pk, format=None): try: wf_module = WfModule.objects.get(pk=pk) except WfModule.DoesNotExist: return HttpResponseNotFound() if request.method in ['POST', 'DELETE', 'PATCH']: if not wf_module.user_authorized_write(request.user): return HttpResponseForbidden() if not wf_module.user_authorized_read(request.user): return HttpResponseNotFound() if request.method == 'GET': serializer = WfModuleSerializer(wf_module) return Response(serializer.data) elif request.method == 'DELETE': DeleteModuleCommand.create(wf_module) return Response(status=status.HTTP_204_NO_CONTENT) elif request.method == 'PATCH': # For patch, we check which fields are set in data, and process all of them try: if not set(request.data.keys()).intersection( {"notes", "auto_update_data", "collapsed"}): raise ValueError('Unknown fields: {}'.format(request.data)) if 'notes' in request.data: patch_notes(wf_module, request.data) if 'auto_update_data' in request.data: patch_update_settings(wf_module, request.data) if 'collapsed' in request.data: wf_module.set_is_collapsed(request.data['collapsed'], notify=False) except Exception as e: return Response({ 'message': str(e), 'status_code': 400 }, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT)
def make_init_state(request, workflow=None, modules=None): """ Build a dict to embed as JSON in `window.initState` in HTML. Raise Http404 if the workflow disappeared. """ ret = {} if workflow: try: with workflow.cooperative_lock(): # raise DoesNotExist on race ret['workflowId'] = workflow.id ret['workflow'] = WorkflowSerializer(workflow, context={ 'request': request }).data tabs = list(workflow.live_tabs) ret['tabs'] = dict( (str(tab.slug), TabSerializer(tab).data) for tab in tabs) wf_modules = list(WfModule.live_in_workflow(workflow)) ret['wfModules'] = { str(wfm.id): WfModuleSerializer(wfm).data for wfm in wf_modules } except Workflow.DoesNotExist: raise Http404('Workflow was recently deleted') ret['uploadConfig'] = { 'bucket': minio.UserFilesBucket, 'accessKey': settings.MINIO_ACCESS_KEY, # never _SECRET_KEY 'server': settings.MINIO_EXTERNAL_URL } ret['user_files_bucket'] = minio.UserFilesBucket if modules: modules_data_list = ModuleSerializer(modules, many=True).data ret['modules'] = dict([(str(m['id_name']), m) for m in modules_data_list]) if request.user.is_authenticated: ret['loggedInUser'] = UserSerializer(request.user).data return ret
def make_init_state(request, workflow=None, modules=None): """ Build a dict to embed as JSON in `window.initState` in HTML. Raise Http404 if the workflow disappeared. Side-effect: update workflow.last_viewed_at. """ ret = {} if workflow: try: with workflow.cooperative_lock(): # raise DoesNotExist on race ret['workflowId'] = workflow.id ret['workflow'] = WorkflowSerializer(workflow, context={ 'request': request }).data tabs = list(workflow.live_tabs) ret['tabs'] = dict( (str(tab.slug), TabSerializer(tab).data) for tab in tabs) wf_modules = list(WfModule.live_in_workflow(workflow)) ret['wfModules'] = { str(wfm.id): WfModuleSerializer(wfm).data for wfm in wf_modules } workflow.last_viewed_at = timezone.now() workflow.save(update_fields=['last_viewed_at']) except Workflow.DoesNotExist: raise Http404('Workflow was recently deleted') if modules: modules_data_list = ModuleSerializer(modules, many=True).data ret['modules'] = dict([(str(m['id_name']), m) for m in modules_data_list]) if request.user.is_authenticated: ret['loggedInUser'] = UserSerializer(request.user).data return ret
def ws_notify(self): """ Notify WebSocket clients that we just undid or redid. This default implementation sends a 'delta' command. It will always include a 'set-workflow' property; it may include a 'set-wf-module' command and may include a 'clear-wf-module' command. """ data = {} with self.workflow.cooperative_lock(): workflow_data = WorkflowSerializer(self.workflow).data # Remove the data we didn't generate correctly because we had no # HTTP request. del workflow_data['is_anonymous'] del workflow_data['owner_name'] del workflow_data['read_only'] data['updateWorkflow'] = _prepare_json(workflow_data) data['updateWfModules'] = {} if hasattr(self, '_changed_wf_module_versions'): for id, delta_id in self._changed_wf_module_versions.items(): data['updateWfModules'][str(id)] = { 'last_relevant_delta_id': delta_id, 'error_msg': '', 'output_columns': None } if hasattr(self, 'wf_module'): self.wf_module.refresh_from_db() if self.wf_module.workflow_id: wf_module_data = WfModuleSerializer(self.wf_module).data data['updateWfModules'][str(self.wf_module_id)] = \ _prepare_json(wf_module_data) else: # When we did or undid this command, we removed the # WfModule from the Workflow. data['clearWfModuleIds'] = [self.wf_module_id] websockets.ws_client_send_delta_sync(self.workflow_id, data)
def load_ws_data(self): workflow = self.workflow data = { 'updateWorkflow': { 'name': workflow.name, 'public': workflow.public, 'last_update': workflow.last_update().isoformat(), }, 'updateWfModules': {} } if hasattr(self, '_changed_wf_module_versions'): for id, delta_id in self._changed_wf_module_versions: data['updateWfModules'][str(id)] = { 'last_relevant_delta_id': delta_id, 'quick_fixes': [], 'output_columns': [], 'output_error': '', 'output_status': 'busy', 'output_n_rows': 0, } if hasattr(self, 'wf_module'): if self.wf_module.is_deleted or self.wf_module.tab.is_deleted: # When we did or undid this command, we removed the # WfModule from the Workflow. data['clearWfModuleIds'] = [self.wf_module_id] else: # Serialize _everything_, including params # # TODO consider serializing only what's changed, so when Alice # changes 'has_header' it doesn't overwrite Bob's 'url' while # he's editing it. wf_module_data = WfModuleSerializer(self.wf_module).data data['updateWfModules'][str(self.wf_module_id)] = \ _prepare_json(wf_module_data) return data
def embed(request, wfmodule_id): try: wf_module = WfModule.objects.get(pk=wfmodule_id) except WfModule.DoesNotExist: wf_module = None if wf_module and (not wf_module.workflow.request_authorized_read(request) or not wf_module.module_version.html_output): wf_module = None if wf_module: workflow_module_serializer = WfModuleSerializer(wf_module) workflow_serializer = WorkflowSerializerLite(wf_module.workflow) init_state = { 'workflow': workflow_serializer.data, 'wf_module': workflow_module_serializer.data } else: init_state = {'workflow': None, 'wf_module': None} response = TemplateResponse(request, 'embed.html', {'initState': init_state}) return response
def workflow_addmodule(request: HttpRequest, workflow: Workflow): module_id = int(request.data['moduleId']) index = int(request.data['index']) try: module = Module.objects.get(pk=module_id) except Module.DoesNotExist: return Response(status=status.HTTP_400_BAD_REQUEST) try: values = dict(request.data['values']) except KeyError: values = {} except TypeError: return Response(status=status.HTTP_400_BAD_REQUEST) # don't allow python code module in anonymous workflow if module.id_name == 'pythoncode' and workflow.is_anonymous: return HttpResponseForbidden() # always add the latest version of a module (do we need ordering on the # objects to ensure last is always latest?) module_version = ModuleVersion.objects.filter(module=module).last() server.utils.log_user_event(request, 'ADD STEP ' + module.name, { 'name': module.name, 'id_name': module.id_name }) delta = async_to_sync(AddModuleCommand.create)(workflow, module_version, index, values) serializer = WfModuleSerializer(delta.wf_module) wfmodule_data = serializer.data return Response({ 'wfModule': wfmodule_data, 'index': index, }, status.HTTP_201_CREATED)
def test_workflow_addmodule_put(self): pk_workflow = Workflow.objects.get(name='Workflow 1').id self.assertEqual(WfModule.objects.filter(workflow=pk_workflow).count(), 0) module1 = Module.objects.get(name='Module 1') module2 = Module.objects.get(name='Module 2') module3 = Module.objects.get(name='Module 3') # add to empty stack request = self.factory.put('/api/workflows/%d/addmodule/' % pk_workflow, {'moduleId': module1.id, 'insertBefore': 0}) force_authenticate(request, user=self.user) response = workflow_addmodule(request, pk=pk_workflow) self.assertIs(response.status_code, status.HTTP_201_CREATED) self.assertEqual(WfModule.objects.filter(workflow=pk_workflow).count(), 1) wfm1 = WfModule.objects.filter(module_version__module=module1.id).first() self.assertEqual(response.data['id'], wfm1.id) # we should get a full serialization back, same as /wfmodules/xx call serializer = WfModuleSerializer(wfm1) # addmodule serialization should also return insert_before property return_data = serializer.data return_data['insert_before'] = '0' self.assertEqual(response.data, return_data) # insert before first module request = self.factory.put('/api/workflows/%d/addmodule/' % pk_workflow, {'moduleId': module2.id, 'insertBefore': 0}) force_authenticate(request, user=self.user) response = workflow_addmodule(request, pk=pk_workflow) self.assertIs(response.status_code, status.HTTP_201_CREATED) self.assertEqual(WfModule.objects.filter(workflow=pk_workflow).count(), 2) wfm2 = WfModule.objects.filter(module_version__module=module2.id).first() self.assertEqual(response.data['id'], wfm2.id) # insert before second module request = self.factory.put('/api/workflows/%d/addmodule/' % pk_workflow, {'moduleId': module3.id, 'insertBefore': 1}) force_authenticate(request, user=self.user) response = workflow_addmodule(request, pk=pk_workflow) self.assertIs(response.status_code, status.HTTP_201_CREATED) self.assertEqual(WfModule.objects.filter(workflow=pk_workflow).count(), 3) wfm3 = WfModule.objects.filter(module_version__module=module3.id).first() self.assertEqual(response.data['id'], wfm3.id) # check for correct insertion order self.assertEqual(list(WfModule.objects.values_list('module_version', flat=True)), [ModuleVersion.objects.get(module=module2).id, ModuleVersion.objects.get(module=module3).id, ModuleVersion.objects.get(module=module1).id]) # bad workflow id request = self.factory.put('/api/workflows/%d/addmodule/' % 10000, {'moduleId': module1.id, 'insertBefore': 0}) force_authenticate(request, user=self.user) response = workflow_addmodule(request, pk=10000) self.assertIs(response.status_code, status.HTTP_404_NOT_FOUND) # bad module id request = self.factory.put('/api/workflows/%d/addmodule/' % Workflow.objects.get(name='Workflow 1').id, {'moduleId': 10000, 'insertBefore': 0}) force_authenticate(request, user=self.user) response = workflow_addmodule(request, pk=Workflow.objects.get(name='Workflow 1').id) self.assertIs(response.status_code, status.HTTP_400_BAD_REQUEST)
def wfmodule_detail(request, pk, format=None): if request.method in ['HEAD', 'GET']: wf_module = _lookup_wf_module_for_read(pk, request) else: wf_module = _lookup_wf_module_for_write(pk, request) if request.method == 'GET': # No need to execute_and_wait(): out-of-date response is fine with wf_module.workflow.cooperative_lock(): serializer = WfModuleSerializer(wf_module) return Response(serializer.data) elif request.method == 'DELETE': delta = async_to_sync(DeleteModuleCommand.create)(wf_module) if delta: return Response(status=status.HTTP_204_NO_CONTENT) else: # missing wf_module; can happen if two DELETE requests race. return HttpResponseNotFound() elif request.method == 'PATCH': # For patch, we check which fields are set in data, and process all of # them # TODO: replace all of these with the generic patch method, most of # this is unnecessary try: if not set(request.data.keys()).intersection( {'notes', 'auto_update_data', 'collapsed', 'notifications'}): raise ValueError('Unknown fields: {}'.format(request.data)) if 'notes' in request.data: patch_notes(wf_module, request.data) if 'auto_update_data' in request.data: patch_update_settings(wf_module, request.data, request) if bool(request.data['auto_update_data']): server.utils.log_user_event(request, 'Enabled auto-update', {'wfModuleId': wf_module.id}) if 'collapsed' in request.data: wf_module.is_collapsed = request.data['collapsed'] wf_module.save(update_fields=['is_collapsed']) if 'notifications' in request.data: notifications = bool(request.data['notifications']) wf_module.notifications = notifications wf_module.save(update_fields=['notifications']) if notifications: server.utils.log_user_event(request, 'Enabled email notifications', {'wfModuleId': wf_module.id}) except ValueError as e: # TODO make this less generic return Response({ 'message': str(e), 'status_code': 400 }, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT)
def patch_wfmodule(wf_module, data): # Just patch it using the built-in Django Rest Framework methods. with wf_module.workflow.cooperative_lock(): serializer = WfModuleSerializer(wf_module, data, partial=True) if serializer.is_valid(): serializer.save()