Ejemplo n.º 1
0
 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
             },
         }
Ejemplo n.º 2
0
    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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
 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
             },
         }
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
    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
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
    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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
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()