Exemple #1
0
 def validate_field(self, value, bad_samples, validate_leading_chars=True, max_length=MAX_LENGTH_SAMPLE_NAME):
     exists = False
     if value:
         exists = True
         
         if not is_valid_chars(value):
             bad_samples.append(value)
         
         if validate_leading_chars and value not in bad_samples and is_invalid_leading_chars(value):
             bad_samples.append(value)
         
         if value not in bad_samples and not is_valid_length(value, max_length):
             bad_samples.append(value)
     
     return exists
Exemple #2
0
def _validate_sample(input, selectedTemplate, planObj):
    errorMsg = None
    sampleDisplayedName = ""
    
    if not input:
        errorMsg = "Required column is empty"
    else:
        if not is_valid_chars(input):
            errorMsg = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_CHARS        
        elif is_invalid_leading_chars(input):
            errorMsg = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LEADING_CHARS                       
        else:
            value = input.strip()
            if value:
                if not is_valid_length(value, iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME):
                    errorMsg = "Sample name" +  iondb.rundb.plan.views.ERROR_MSG_INVALID_LENGTH  %(str(iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME))
                else:                            
                    sampleDisplayedName = value
                    sample = value.replace(' ', '_')
                    
    return errorMsg, sampleDisplayedName
Exemple #3
0
    def validate_field(self,
                       value,
                       bad_samples,
                       validate_leading_chars=True,
                       max_length=MAX_LENGTH_SAMPLE_NAME):
        exists = False
        if value:
            exists = True

            if not is_valid_chars(value):
                bad_samples.append(value)

            if validate_leading_chars and value not in bad_samples and is_invalid_leading_chars(
                    value):
                bad_samples.append(value)

            if value not in bad_samples and not is_valid_length(
                    value, max_length):
                bad_samples.append(value)

        return exists
Exemple #4
0
def _validate_sample(input, selectedTemplate, planObj):
    errorMsg = None
    sampleDisplayedName = ""

    if not input:
        errorMsg = "Required column is empty"
    else:
        if not is_valid_chars(input):
            errorMsg = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_CHARS
        elif is_invalid_leading_chars(input):
            errorMsg = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LEADING_CHARS
        else:
            value = input.strip()
            if value:
                if not is_valid_length(
                        value, iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME):
                    errorMsg = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LENGTH % (
                        str(iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME))
                else:
                    sampleDisplayedName = value
                    sample = value.replace(' ', '_')

    return errorMsg, sampleDisplayedName
Exemple #5
0
def _validate_barcodedSamples(input, selectedTemplate, barcodeKitName, planObj):
    errorMsg = None
    barcodedSampleJson = {}
        
    #{"bc10_noPE_sample3":{"26":"IonXpress_010"},"bc04_noPE_sample1":{"20":"IonXpress_004"},"bc08_noPE_sample2":{"24":"IonXpress_008"}}
    #20121122-new JSON format
    #{"bcSample1":{"barcodes":["IonXpress_001","IonXpress_002"]},"bcSample2":{"barcodes":["IonXpress_003"]}}
        
    barcodes = list(dnaBarcode.objects.filter(name = barcodeKitName).values('id', 'id_str').order_by('id_str'))
    
    if len(barcodes) == 0:
        errorMsg = "Barcode "+ barcodeKitName + " cannot be found. " 
        return errorMsg, barcodedSampleJson
    
    errorMsgDict = {}
    try:
        for barcode in barcodes:            
            key = barcode["id_str"] + plan_csv_writer.COLUMN_BC_SAMPLE_KEY
            sample = input.get(key, "")        

            if sample:           
                if not is_valid_chars(sample):
                    errorMsgDict[key] = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_CHARS
                elif is_invalid_leading_chars(sample):
                    errorMsgDict[key] = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LEADING_CHARS        
                else:
                    value = sample.strip()

                    if value:
                        if not is_valid_length(value, iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME):
                            errorMsgDict[key] = "Sample name" +  iondb.rundb.plan.views.ERROR_MSG_INVALID_LENGTH  %(str(iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME))                           
                        else:     
                            barcodedSample = barcodedSampleJson.get(value, {})
                    
                            if barcodedSample:
                                barcodeList = barcodedSample.get("barcodes", [])
                                if barcodeList:
                                    barcodeList.append(barcode["id_str"])
                                else:
                                    barcodeDict = {
                                                   "barcodes" : [barcode["id_str"]]
                                                   }

                                barcodedSampleJson[sample.strip()] = barcodeDict                                          
                            else:              
                                barcodeDict = {
                                               "barcodes" : [barcode["id_str"]]
                                               }

                                barcodedSampleJson[sample.strip()] = barcodeDict                                
    except:
        logger.exception(format_exc())  
        errorMsg = "Internal error during barcoded sample processing"
    
    if errorMsgDict:
        return simplejson.dumps(errorMsgDict), barcodedSampleJson
    
    if not barcodedSampleJson:
        errorMsg = "Required column is empty. At least one barcoded sample is required. "
    else:
        planObj.get_easObj().barcodedSamples = barcodedSampleJson

    return errorMsg, barcodedSampleJson
Exemple #6
0
def save_plan_or_template(request, planOid):
    """
    Saving new or edited plan/template to db (source: plan template wizard)
    Editing a planned run from having 1 sample to 2 samples will result in one edited planned run and one new planned run
    """
    def isReusable(submitIntent):
        return not (submitIntent == 'savePlan' or submitIntent == 'updatePlan')

    if request.method != 'POST':
        logger.exception(format_exc())
        return HttpResponse(json.dumps({
            "error":
            "Error, unsupported HTTP Request method (%s) for plan update." %
            request.method
        }),
                            mimetype="application/json")

    # Process Inputs

    # pylint:disable=E1103
    json_data = simplejson.loads(request.raw_post_data)
    submitIntent = json_data.get('submitIntent', '')
    logger.debug(
        'views.save_plan_or_template POST.raw_post_data... simplejson Data: "%s"'
        % json_data)
    logger.debug("views.save_plan_or_template submitIntent=%s" % submitIntent)
    # saving Template or Planned Run
    isReusable = isReusable(submitIntent)
    runModeValue = json_data.get('runMode', 'single')
    isPlanGroupValue = runModeValue == 'pe' and not isReusable
    libraryKeyValue = json_data.get('libraryKey', '')
    forward3primeAdapterValue = json_data.get('forward3primeAdapter', '')

    msgvalue = 'Run Plan' if not isReusable else 'Template'
    if runModeValue == 'pe':
        return HttpResponse(json.dumps({
            "error":
            "Error, paired-end plan is no longer supported. %s will not be saved."
            % (msgvalue)
        }),
                            mimetype="application/html")

    planDisplayedNameValue = json_data.get('planDisplayedName', '').strip()
    noteValue = json_data.get('notes_workaround', '')

    # perform server-side validation to avoid things falling through the crack
    if not planDisplayedNameValue:
        return HttpResponse(json.dumps(
            {"error": "Error, please enter a %s Name." % (msgvalue)}),
                            mimetype="application/html")

    if not is_valid_chars(planDisplayedNameValue):
        return HttpResponse(json.dumps(
            {"error":
             "Error, %s Name" % (msgvalue) + ERROR_MSG_INVALID_CHARS}),
                            mimetype="application/html")

    if not is_valid_length(planDisplayedNameValue, MAX_LENGTH_PLAN_NAME):
        return HttpResponse(json.dumps({
            "error":
            "Error, %s Name" % (msgvalue) + ERROR_MSG_INVALID_LENGTH %
            (str(MAX_LENGTH_PLAN_NAME))
        }),
                            mimetype="application/html")

    if noteValue:
        if not is_valid_chars(noteValue):
            return HttpResponse(json.dumps({
                "error":
                "Error, %s note" % (msgvalue) + ERROR_MSG_INVALID_CHARS
            }),
                                mimetype="application/html")

        if not is_valid_length(noteValue, MAX_LENGTH_NOTES):
            return HttpResponse(json.dumps({
                "error":
                "Error, Note" + ERROR_MSG_INVALID_LENGTH %
                (str(MAX_LENGTH_NOTES))
            }),
                                mimetype="application/html")

    # Projects
    projectObjList = get_projects(request.user, json_data)

    # IonReporterUploader configuration and samples
    selectedPlugins = json_data.get('selectedPlugins', {})
    IRconfigList = json_data.get('irConfigList', [])

    IRU_selected = False
    for uploader in selectedPlugins.values():
        if 'ionreporteruploader' in uploader['name'].lower(
        ) and uploader['name'] != 'IonReporterUploader_V1_0':
            IRU_selected = True

    #if IRU is set to autoRun, user does not need to select the plugin explicitly. user could have set all IRU versions to autorun
    IRU_autorun_count = 0
    if not IRU_selected:
        IRU_autoruns = Plugin.objects.filter(
            name__icontains="IonReporter",
            selected=True,
            active=True,
            autorun=True).exclude(
                name__icontains="IonReporterUploader_V1_0").order_by('-name')
        IRU_autorun_count = IRU_autoruns.count()
        if IRU_autorun_count > 0:
            IRU_selected = True

    if IRU_selected:
        samples_IRconfig = json_data.get('sample_irConfig', '')

        if samples_IRconfig:
            samples_IRconfig = ','.join(samples_IRconfig)

        #generate UUID for unique setIds
        id_uuid = {}
        setids = [ir.get('setid', "") for ir in IRconfigList]

        if setids:
            for setid in set(setids):
                if setid:
                    id_uuid[setid] = str(uuid.uuid4())
            for ir_config in IRconfigList:
                setid = ir_config.get('setid', '')

                if setid:
                    ir_config['setid'] += '__' + id_uuid[setid]

        if IRU_autorun_count > 0 and not samples_IRconfig:
            #if more than one IRU version is set to autorun and user does not explicitly select one,
            #gui shows workflow config for IRU v1.0
            samples_IRconfig = json_data.get('samples_workaround', '')

    # Samples
    barcodeIdValue = json_data.get('barcodeId', '')
    barcodedSamples = ''
    sampleValidationErrorMsg = ''
    sampleValidationErrorMsg_leadingChars = ''
    sampleValidationErrorMsg_length = ''

    # one Plan will be created per entry in sampleList
    # samples for barcoded Plan have a separate field (barcodedSamples)

    if isReusable:
        # samples entered only when saving planned run (not template)
        sampleList = ['']
    elif barcodeIdValue:
        # a barcode Set is selected
        sampleList = ['']
        bcSamplesValues = json_data.get('bcSamples_workaround', '')
        bcDictionary = {}
        bcId = ""
        for token in bcSamplesValues.split(","):
            if ((token.find("bcKey|")) == 0):
                bcId, bcId_str = token.split("|")[1:]
            else:
                sample = token.strip()
                if bcId and sample:
                    if not is_valid_chars(sample):
                        sampleValidationErrorMsg += sample + ', '
                    elif is_invalid_leading_chars(sample):
                        sampleValidationErrorMsg_leadingChars += sample + ", "
                    elif not is_valid_length(sample, MAX_LENGTH_SAMPLE_NAME):
                        sampleValidationErrorMsg_length += sample + ", "

                    bcDictionary.setdefault(sample,
                                            {}).setdefault('barcodes',
                                                           []).append(bcId_str)
                bcId = ""

        barcodedSamples = simplejson.dumps(bcDictionary)
        logger.debug(
            "views.save_plan_or_template after simplejson.dumps... barcodedSamples=%s;"
            % (barcodedSamples))

        if not bcDictionary:
            transaction.rollback()
            return HttpResponse(json.dumps({
                "error":
                "Error, please enter at least one barcode sample name."
            }),
                                mimetype="application/html")

    else:
        # Non-barcoded samples
        sampleList = []
        if IRU_selected:
            samples = samples_IRconfig
        else:
            samples = json_data.get('samples_workaround', '')

        for sample in samples.split(','):
            if sample.strip():
                if not is_valid_chars(sample):
                    sampleValidationErrorMsg += sample + ', '
                elif is_invalid_leading_chars(sample):
                    sampleValidationErrorMsg_leadingChars += sample + ", "
                elif not is_valid_length(sample, MAX_LENGTH_SAMPLE_NAME):
                    sampleValidationErrorMsg_length += sample + ", "
                else:
                    sampleList.append(sample)

        logger.debug("views.save_plan_or_template sampleList=%s " %
                     (sampleList))

        if len(
                sampleList
        ) == 0 and not sampleValidationErrorMsg and not sampleValidationErrorMsg_leadingChars and not sampleValidationErrorMsg_length:
            transaction.rollback()
            return HttpResponse(json.dumps({
                "error":
                "Error, please enter a sample name for the run plan."
            }),
                                mimetype="application/html")

    # Samples validation
    if sampleValidationErrorMsg or sampleValidationErrorMsg_leadingChars or sampleValidationErrorMsg_length:
        message = ""
        if sampleValidationErrorMsg:
            message = "Error, sample name" + ERROR_MSG_INVALID_CHARS
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg + '<br>'
        if sampleValidationErrorMsg_leadingChars:
            message = message + "Error, sample name" + ERROR_MSG_INVALID_LEADING_CHARS
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg_leadingChars + '<br>'
        if sampleValidationErrorMsg_length:
            message = message + "Error, sample name" + ERROR_MSG_INVALID_LENGTH % (
                str(MAX_LENGTH_SAMPLE_NAME))
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg_length

        transaction.rollback()
        return HttpResponse(json.dumps({"error": message}),
                            mimetype="application/html")

    selectedPluginsValue = json_data.get('selectedPlugins', {})

    # end processing input data

    # Edit/Create Plan(s)

    if int(planOid) == 0:
        edit_existing_plan = False
    else:
        edit_existing_plan = True

    for i, sample in enumerate(sampleList):
        logger.debug(
            "...LOOP... views.save_plan_or_template SAMPLE=%s; isSystem=%s; isReusable=%s; isPlanGroup=%s "
            % (sample.strip(), json_data["isSystem"], isReusable,
               isPlanGroupValue))

        # add IonReporter config values for each sample
        if len(IRconfigList) > 0:
            for uploader in selectedPluginsValue.values():
                if 'ionreporteruploader' in uploader['name'].lower():
                    if len(IRconfigList) > 1 and not barcodeIdValue:
                        uploader['userInput'] = [IRconfigList[i]]
                    else:
                        uploader['userInput'] = IRconfigList

        if len(sampleList) > 1:
            inputPlanDisplayedName = planDisplayedNameValue + '_' + sample.strip(
            )
        else:
            inputPlanDisplayedName = planDisplayedNameValue

        selectedTemplatingKit = json_data.get('templatekitname', '')
        samplePrepInstrumentType = json_data.get('samplePrepInstrumentType',
                                                 '')
        if samplePrepInstrumentType == 'ionChef':
            selectedTemplatingKit = json_data.get('templatekitionchefname', '')

        #PDD-TODO: remove the x_ prefix. the x_ prefix is just a reminder what the obsolete attributes to remove during the next phase
        kwargs = {
            'planDisplayedName':
            inputPlanDisplayedName,
            "planName":
            inputPlanDisplayedName.replace(' ', '_'),
            'usePreBeadfind':
            toBoolean(json_data['usePreBeadfind'], False),
            'usePostBeadfind':
            toBoolean(json_data['usePostBeadfind'], False),
            'preAnalysis':
            True,
            'runType':
            json_data['runType'],
            'templatingKitName':
            selectedTemplatingKit,
            'controlSequencekitname':
            json_data.get('controlsequence', ''),
            'runMode':
            runModeValue,
            'isSystem':
            toBoolean(json_data['isSystem'], False),
            'isReusable':
            isReusable,
            'isPlanGroup':
            isPlanGroupValue,
            'username':
            request.user.username,
            'isFavorite':
            toBoolean(json_data.get('isFavorite', 'False'), False),
            'pairedEndLibraryAdapterName':
            json_data.get('pairedEndLibraryAdapterName', ''),
            'samplePrepKitName':
            json_data.get('samplePrepKitName', ''),
            'planStatus':
            "planned",
            'x_autoAnalyze':
            True,
            'x_barcodedSamples':
            barcodedSamples,
            'x_barcodeId':
            barcodeIdValue,
            'x_bedfile':
            json_data.get('bedfile', ''),
            'x_chipType':
            json_data.get('chipType', ''),
            'x_flows':
            json_data.get('flows', None),
            'x_forward3primeadapter':
            forward3primeAdapterValue,
            ###'_isReverseRun':  = self.isReverseRun
            'x_library':
            json_data.get('library', ''),
            'x_libraryKey':
            libraryKeyValue,
            'x_librarykitname':
            json_data.get('librarykitname', ''),
            'x_notes':
            noteValue,
            'x_regionfile':
            json_data.get('regionfile', ''),
            'x_sample':
            sample.strip().replace(' ', '_'),
            'x_sampleDisplayedName':
            sample.strip(),
            'x_selectedPlugins':
            selectedPluginsValue,
            'x_sequencekitname':
            json_data.get('sequencekitname', ''),
            'x_variantfrequency':
            json_data.get('variantfrequency', ''),
        }

        planTemplate = None

        #if we're changing a plan from having 1 sample to say 2 samples, we need to UPDATE 1 plan and CREATE 1 plan!!
        try:
            if not edit_existing_plan:
                planTemplate, extra_kwargs = PlannedExperiment.objects.save_plan(
                    -1, **kwargs)
            else:
                planTemplate, extra_kwargs = PlannedExperiment.objects.save_plan(
                    planOid, **kwargs)

                edit_existing_plan = False

            # Update QCtype thresholds
            qcTypes = QCType.objects.all()
            for qcType in qcTypes:
                qc_threshold = json_data.get(qcType.qcName, '')
                if qc_threshold:
                    # get existing PlannedExperimentQC if any
                    plannedExpQcs = PlannedExperimentQC.objects.filter(
                        plannedExperiment=planTemplate.id, qcType=qcType.id)
                    if len(plannedExpQcs) > 0:
                        for plannedExpQc in plannedExpQcs:
                            plannedExpQc.threshold = qc_threshold
                            plannedExpQc.save()
                    else:
                        kwargs = {
                            'plannedExperiment': planTemplate,
                            'qcType': qcType,
                            'threshold': qc_threshold
                        }
                        plannedExpQc = PlannedExperimentQC(**kwargs)
                        plannedExpQc.save()

            # add/remove projects
            if projectObjList:
                #TODO: refactor this logic to simplify using django orm
                projectNameList = [project.name for project in projectObjList]
                for currentProject in planTemplate.projects.all():
                    if currentProject.name not in projectNameList:
                        planTemplate.projects.remove(currentProject)
                for projectObj in projectObjList:
                    planTemplate.projects.add(projectObj)
            else:
                planTemplate.projects.clear()

        except ValidationError, err:
            transaction.rollback()
            logger.exception(format_exc())

            message = "Internal error while trying to save the plan. "
            for msg in err.messages:
                message += str(msg)
                message += " "

            return HttpResponse(json.dumps({"error": message}),
                                mimetype="application/json")

        except Exception as excp:
            transaction.rollback()
            logger.exception(format_exc())

            message = "Internal error while trying to save the plan. %s" % (
                excp.message)
            return HttpResponse(json.dumps({"error": message}),
                                mimetype="application/json")
Exemple #7
0
def _validate_barcodedSamples(input, selectedTemplate, barcodeKitName,
                              planObj):
    errorMsg = None
    barcodedSampleJson = {}

    #{"bc10_noPE_sample3":{"26":"IonXpress_010"},"bc04_noPE_sample1":{"20":"IonXpress_004"},"bc08_noPE_sample2":{"24":"IonXpress_008"}}
    #20121122-new JSON format
    #{"bcSample1":{"barcodes":["IonXpress_001","IonXpress_002"]},"bcSample2":{"barcodes":["IonXpress_003"]}}

    barcodes = list(
        dnaBarcode.objects.filter(name=barcodeKitName).values(
            'id', 'id_str').order_by('id_str'))

    if len(barcodes) == 0:
        errorMsg = "Barcode " + barcodeKitName + " cannot be found. "
        return errorMsg, barcodedSampleJson

    errorMsgDict = {}
    try:
        for barcode in barcodes:
            key = barcode["id_str"] + plan_csv_writer.COLUMN_BC_SAMPLE_KEY
            sample = input.get(key, "")

            if sample:
                if not is_valid_chars(sample):
                    errorMsgDict[
                        key] = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_CHARS
                elif is_invalid_leading_chars(sample):
                    errorMsgDict[
                        key] = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LEADING_CHARS
                else:
                    value = sample.strip()

                    if value:
                        if not is_valid_length(
                                value,
                                iondb.rundb.plan.views.MAX_LENGTH_SAMPLE_NAME):
                            errorMsgDict[
                                key] = "Sample name" + iondb.rundb.plan.views.ERROR_MSG_INVALID_LENGTH % (
                                    str(iondb.rundb.plan.views.
                                        MAX_LENGTH_SAMPLE_NAME))
                        else:
                            barcodedSample = barcodedSampleJson.get(value, {})

                            if barcodedSample:
                                barcodeList = barcodedSample.get(
                                    "barcodes", [])
                                if barcodeList:
                                    barcodeList.append(barcode["id_str"])
                                else:
                                    barcodeDict = {
                                        "barcodes": [barcode["id_str"]]
                                    }

                                barcodedSampleJson[
                                    sample.strip()] = barcodeDict
                            else:
                                barcodeDict = {"barcodes": [barcode["id_str"]]}

                                barcodedSampleJson[
                                    sample.strip()] = barcodeDict
    except:
        logger.exception(format_exc())
        errorMsg = "Internal error during barcoded sample processing"

    if errorMsgDict:
        return simplejson.dumps(errorMsgDict), barcodedSampleJson

    if not barcodedSampleJson:
        errorMsg = "Required column is empty. At least one barcoded sample is required. "
    else:
        planObj.get_easObj().barcodedSamples = barcodedSampleJson

    return errorMsg, barcodedSampleJson
Exemple #8
0
def save_plan_or_template(request, planOid):
    """
    Saving new or edited plan/template to db (source: plan template wizard)
    Editing a planned run from having 1 sample to 2 samples will result in one edited planned run and one new planned run
    """
    def isReusable(submitIntent):
        return not (submitIntent == 'savePlan' or submitIntent == 'updatePlan')


    if request.method != 'POST':
        logger.exception(format_exc())
        return HttpResponse(json.dumps({"error": "Error, unsupported HTTP Request method (%s) for plan update." % request.method}), mimetype="application/json")

    # Process Inputs

    # pylint:disable=E1103
    json_data = simplejson.loads(request.raw_post_data)
    submitIntent = json_data.get('submitIntent', '')
    logger.debug('views.save_plan_or_template POST.raw_post_data... simplejson Data: "%s"' % json_data)
    logger.debug("views.save_plan_or_template submitIntent=%s" % submitIntent)
    # saving Template or Planned Run
    isReusable = isReusable(submitIntent)
    runModeValue = json_data.get('runMode', 'single')
    isPlanGroupValue = runModeValue == 'pe' and not isReusable
    libraryKeyValue = json_data.get('libraryKey', '')
    forward3primeAdapterValue = json_data.get('forward3primeAdapter', '')

    msgvalue = 'Run Plan' if not isReusable else 'Template'
    if runModeValue == 'pe':
        return HttpResponse(json.dumps({"error": "Error, paired-end plan is no longer supported. %s will not be saved." % (msgvalue)}), mimetype="application/html")
    
    planDisplayedNameValue = json_data.get('planDisplayedName', '').strip()
    noteValue = json_data.get('notes_workaround', '')

    # perform server-side validation to avoid things falling through the crack    
    if not planDisplayedNameValue:
        return HttpResponse(json.dumps({"error": "Error, please enter a %s Name."  %(msgvalue)}), mimetype="application/html")

    if not is_valid_chars(planDisplayedNameValue):
        return HttpResponse(json.dumps({"error": "Error, %s Name" %(msgvalue) + ERROR_MSG_INVALID_CHARS}), mimetype="application/html")        
        
    if not is_valid_length(planDisplayedNameValue, MAX_LENGTH_PLAN_NAME):
        return HttpResponse(json.dumps({"error": "Error, %s Name"  %(msgvalue) + ERROR_MSG_INVALID_LENGTH  %(str(MAX_LENGTH_PLAN_NAME))}), mimetype="application/html")

    if noteValue:
        if not is_valid_chars(noteValue):
            return HttpResponse(json.dumps({"error": "Error, %s note" %(msgvalue) + ERROR_MSG_INVALID_CHARS}), mimetype="application/html")
        
        if not is_valid_length(noteValue, MAX_LENGTH_NOTES):
            return HttpResponse(json.dumps({"error": "Error, Note" + ERROR_MSG_INVALID_LENGTH  %(str(MAX_LENGTH_NOTES))}), mimetype="application/html")

    # Projects
    projectObjList = get_projects(request.user, json_data)

    # IonReporterUploader configuration and samples
    selectedPlugins = json_data.get('selectedPlugins', {})
    IRconfigList = json_data.get('irConfigList', [])

    IRU_selected = False
    for uploader in selectedPlugins.values():
        if 'ionreporteruploader' in uploader['name'].lower() and uploader['name'] != 'IonReporterUploader_V1_0':
            IRU_selected = True

    #if IRU is set to autoRun, user does not need to select the plugin explicitly. user could have set all IRU versions to autorun
    IRU_autorun_count = 0
    if not IRU_selected:
        IRU_autoruns = Plugin.objects.filter(name__icontains="IonReporter", selected=True, active=True, autorun=True).exclude(name__icontains="IonReporterUploader_V1_0").order_by('-name')
        IRU_autorun_count = IRU_autoruns.count()
        if IRU_autorun_count > 0:
            IRU_selected = True
    
    if IRU_selected:
        samples_IRconfig = json_data.get('sample_irConfig', '')

        if samples_IRconfig:
            samples_IRconfig = ','.join(samples_IRconfig)

        #generate UUID for unique setIds
        id_uuid = {}
        setids = [ir.get('setid', "") for ir in IRconfigList]

        if setids:
            for setid in set(setids):
                if setid:                    
                    id_uuid[setid] = str(uuid.uuid4())
            for ir_config in IRconfigList:
                setid = ir_config.get('setid', '')
                                
                if setid:
                    ir_config['setid'] += '__' + id_uuid[setid]

        if IRU_autorun_count > 0 and not samples_IRconfig:
            #if more than one IRU version is set to autorun and user does not explicitly select one, 
            #gui shows workflow config for IRU v1.0
            samples_IRconfig = json_data.get('samples_workaround', '')
        
    # Samples
    barcodeIdValue = json_data.get('barcodeId', '')
    barcodedSamples = ''
    sampleValidationErrorMsg = ''
    sampleValidationErrorMsg_leadingChars = ''
    sampleValidationErrorMsg_length = ''
        
    # one Plan will be created per entry in sampleList
    # samples for barcoded Plan have a separate field (barcodedSamples)

    if isReusable:
        # samples entered only when saving planned run (not template)
        sampleList = ['']
    elif barcodeIdValue:
        # a barcode Set is selected
        sampleList = ['']
        bcSamplesValues = json_data.get('bcSamples_workaround', '')
        bcDictionary = {}
        bcId = ""
        for token in bcSamplesValues.split(","):
            if ((token.find("bcKey|")) == 0):
                bcId, bcId_str = token.split("|")[1:]
            else:
                sample = token.strip()
                if bcId and sample:
                    if not is_valid_chars(sample):
                        sampleValidationErrorMsg += sample + ', '
                    elif is_invalid_leading_chars(sample):
                        sampleValidationErrorMsg_leadingChars += sample + ", "
                    elif not is_valid_length(sample, MAX_LENGTH_SAMPLE_NAME):
                        sampleValidationErrorMsg_length += sample + ", "
                        
                    bcDictionary.setdefault(sample, {}).setdefault('barcodes',[]).append(bcId_str)
                bcId = ""

        barcodedSamples = simplejson.dumps(bcDictionary)
        logger.debug("views.save_plan_or_template after simplejson.dumps... barcodedSamples=%s;" % (barcodedSamples))

        if not bcDictionary:
            transaction.rollback()
            return HttpResponse(json.dumps({"error": "Error, please enter at least one barcode sample name."}), mimetype="application/html")

    else:
        # Non-barcoded samples
        sampleList = []
        if IRU_selected:
            samples = samples_IRconfig
        else:
            samples = json_data.get('samples_workaround', '')

        for sample in samples.split(','):
            if sample.strip():
                if not is_valid_chars(sample):
                    sampleValidationErrorMsg += sample + ', '
                elif is_invalid_leading_chars(sample):
                    sampleValidationErrorMsg_leadingChars += sample + ", "
                elif not is_valid_length(sample, MAX_LENGTH_SAMPLE_NAME):
                    sampleValidationErrorMsg_length += sample + ", "
                else:
                    sampleList.append(sample)

        logger.debug("views.save_plan_or_template sampleList=%s " % (sampleList))

        if  len(sampleList) == 0 and not sampleValidationErrorMsg and not sampleValidationErrorMsg_leadingChars and not sampleValidationErrorMsg_length:
            transaction.rollback()
            return HttpResponse(json.dumps({"error": "Error, please enter a sample name for the run plan."}), mimetype="application/html")
    
    # Samples validation
    if sampleValidationErrorMsg or sampleValidationErrorMsg_leadingChars or sampleValidationErrorMsg_length:
        message = ""
        if sampleValidationErrorMsg:
            message = "Error, sample name" + ERROR_MSG_INVALID_CHARS
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg + '<br>'
        if sampleValidationErrorMsg_leadingChars:
            message = message + "Error, sample name" + ERROR_MSG_INVALID_LEADING_CHARS
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg_leadingChars + '<br>'
        if sampleValidationErrorMsg_length:
            message = message + "Error, sample name" + ERROR_MSG_INVALID_LENGTH  %(str(MAX_LENGTH_SAMPLE_NAME))
            message = message + ' <br>Please fix: ' + sampleValidationErrorMsg_length
          
        transaction.rollback()
        return HttpResponse(json.dumps({"error": message}), mimetype="application/html")

    selectedPluginsValue = json_data.get('selectedPlugins', {})

    # end processing input data

    # Edit/Create Plan(s)

    if int(planOid) == 0:
        edit_existing_plan = False
    else:
        edit_existing_plan = True

    for i, sample in enumerate(sampleList):
        logger.debug("...LOOP... views.save_plan_or_template SAMPLE=%s; isSystem=%s; isReusable=%s; isPlanGroup=%s "
                     % (sample.strip(), json_data["isSystem"], isReusable, isPlanGroupValue))

        # add IonReporter config values for each sample
        if len(IRconfigList) > 0:
            for uploader in selectedPluginsValue.values():
                if 'ionreporteruploader' in uploader['name'].lower():
                    if len(IRconfigList) > 1 and not barcodeIdValue:
                        uploader['userInput'] = [IRconfigList[i]]
                    else:
                        uploader['userInput'] = IRconfigList

        if len(sampleList) > 1:
            inputPlanDisplayedName = planDisplayedNameValue + '_' + sample.strip()
        else:
            inputPlanDisplayedName = planDisplayedNameValue
            
        selectedTemplatingKit = json_data.get('templatekitname', '')
        samplePrepInstrumentType = json_data.get('samplePrepInstrumentType', '')
        if samplePrepInstrumentType == 'ionChef':
            selectedTemplatingKit = json_data.get('templatekitionchefname', '')
        
        #PDD-TODO: remove the x_ prefix. the x_ prefix is just a reminder what the obsolete attributes to remove during the next phase
        kwargs = {
            'planDisplayedName': inputPlanDisplayedName,
            "planName": inputPlanDisplayedName.replace(' ', '_'),
            'usePreBeadfind': toBoolean(json_data['usePreBeadfind'], False),
            'usePostBeadfind': toBoolean(json_data['usePostBeadfind'], False),
            'preAnalysis': True,
            'runType': json_data['runType'],
            'templatingKitName': selectedTemplatingKit,
            'controlSequencekitname': json_data.get('controlsequence', ''),
            'runMode': runModeValue,
            'isSystem': toBoolean(json_data['isSystem'], False),
            'isReusable': isReusable,
            'isPlanGroup': isPlanGroupValue,
            'username': request.user.username,
            'isFavorite': toBoolean(json_data.get('isFavorite', 'False'), False),
            'pairedEndLibraryAdapterName': json_data.get('pairedEndLibraryAdapterName', ''),
            'samplePrepKitName': json_data.get('samplePrepKitName', ''),
            'planStatus' : "planned",

            'x_autoAnalyze': True,
            'x_barcodedSamples': barcodedSamples,
            'x_barcodeId': barcodeIdValue,
            'x_bedfile': json_data.get('bedfile', ''),
            'x_chipType': json_data.get('chipType', ''),
            'x_flows': json_data.get('flows', None),
            'x_forward3primeadapter': forward3primeAdapterValue,
            ###'_isReverseRun':  = self.isReverseRun
            'x_library': json_data.get('library', ''),
            'x_libraryKey': libraryKeyValue,
            'x_librarykitname': json_data.get('librarykitname', ''),
            'x_notes': noteValue,
            'x_regionfile': json_data.get('regionfile', ''),
            'x_sample': sample.strip().replace(' ', '_'),
            'x_sampleDisplayedName': sample.strip(),
            'x_selectedPlugins': selectedPluginsValue,
            'x_sequencekitname': json_data.get('sequencekitname', ''),
            'x_variantfrequency': json_data.get('variantfrequency', ''),
        }

        planTemplate = None
        
        #if we're changing a plan from having 1 sample to say 2 samples, we need to UPDATE 1 plan and CREATE 1 plan!!
        try:
            if not edit_existing_plan:
                planTemplate, extra_kwargs = PlannedExperiment.objects.save_plan(-1, **kwargs)             
            else:
                planTemplate, extra_kwargs = PlannedExperiment.objects.save_plan(planOid, **kwargs)
                
                edit_existing_plan = False

            # Update QCtype thresholds
            qcTypes = QCType.objects.all()
            for qcType in qcTypes:
                qc_threshold = json_data.get(qcType.qcName, '')
                if qc_threshold:
                    # get existing PlannedExperimentQC if any
                    plannedExpQcs = PlannedExperimentQC.objects.filter(plannedExperiment=planTemplate.id, qcType=qcType.id)
                    if len(plannedExpQcs) > 0:
                        for plannedExpQc in plannedExpQcs:
                            plannedExpQc.threshold = qc_threshold
                            plannedExpQc.save()
                    else:
                        kwargs = {
                            'plannedExperiment': planTemplate,
                            'qcType': qcType,
                            'threshold': qc_threshold
                        }
                        plannedExpQc = PlannedExperimentQC(**kwargs)
                        plannedExpQc.save()

            # add/remove projects
            if projectObjList:
                #TODO: refactor this logic to simplify using django orm
                projectNameList = [project.name for project in projectObjList]
                for currentProject in planTemplate.projects.all():
                    if currentProject.name not in projectNameList:
                        planTemplate.projects.remove(currentProject)
                for projectObj in projectObjList:
                    planTemplate.projects.add(projectObj)
            else:
                planTemplate.projects.clear()
                
        except ValidationError, err:
            transaction.rollback()
            logger.exception(format_exc())
            
            message = "Internal error while trying to save the plan. "
            for msg in err.messages:                
                message += str(msg)
                message += " "

            return HttpResponse(json.dumps({"error": message}), mimetype="application/json")

        except Exception as excp:
            transaction.rollback()
            logger.exception(format_exc())

            message = "Internal error while trying to save the plan. %s" %(excp.message)
            return HttpResponse(json.dumps({"error": message}), mimetype="application/json")