def test_prep_template_graph_get_req(self): obs = prep_template_graph_get_req(1, '*****@*****.**') exp = {'edge_list': [(1, 3), (1, 2), (2, 4), (2, 5), (2, 6)], 'node_labels': [(1, 'Raw data 1 - FASTQ'), (2, 'Demultiplexed 1 - Demultiplexed'), (3, 'Demultiplexed 2 - Demultiplexed'), (4, 'BIOM - BIOM'), (5, 'BIOM - BIOM'), (6, 'BIOM - BIOM')], 'status': 'success', 'message': ''} self.assertItemsEqual(obs['edge_list'], exp['edge_list']) self.assertItemsEqual(obs['node_labels'], exp['node_labels']) self.assertEqual(obs['status'], exp['status']) self.assertEqual(obs['message'], exp['message']) Artifact(4).visibility = "public" obs = prep_template_graph_get_req(1, '*****@*****.**') exp = {'edge_list': [(1, 2), (2, 4)], 'node_labels': [(1, 'Raw data 1 - FASTQ'), (2, 'Demultiplexed 1 - Demultiplexed'), (4, 'BIOM - BIOM')], 'status': 'success', 'message': ''} self.assertItemsEqual(obs['edge_list'], exp['edge_list']) self.assertItemsEqual(obs['node_labels'], exp['node_labels']) self.assertEqual(obs['status'], exp['status']) self.assertEqual(obs['message'], exp['message']) # Reset visibility of the artifacts for i in range(4, 0, -1): Artifact(i).visibility = "private"
def artifact_get_prep_req(user_id, artifact_ids): """Returns all prep info sample ids for the given artifact_ids Parameters ---------- user_id : str user making the request artifact_ids : list of int list of artifact ids Returns ------- dict of objects A dictionary containing the artifact information {'status': status, 'message': message, 'data': {artifact_id: [prep info sample ids]} """ samples = {} for aid in sorted(artifact_ids): artifact = Artifact(aid) access_error = check_access(artifact.study.id, user_id) if access_error: return access_error samples[aid] = list( chain(*[sorted(pt.keys()) for pt in Artifact(aid).prep_templates])) return {'status': 'success', 'msg': '', 'data': samples}
def test_artifact_post_req(self): # Create new prep template to attach artifact to pt = npt.assert_warns(QiitaDBWarning, PrepTemplate.create, pd.DataFrame({'new_col': { '1.SKD6.640190': 1 }}), Study(1), '16S') self._files_to_remove.extend([fp for _, fp in pt.get_filepaths()]) new_artifact_id = get_count('qiita.artifact') + 1 filepaths = { 'raw_forward_seqs': 'uploaded_file.txt', 'raw_barcodes': 'update.txt' } obs = artifact_post_req('*****@*****.**', filepaths, 'FASTQ', 'New Test Artifact', pt.id) exp = {'status': 'success', 'message': ''} self.assertEqual(obs, exp) obs = r_client.get('prep_template_%d' % pt.id) self.assertIsNotNone(obs) redis_info = loads(r_client.get(loads(obs)['job_id'])) while redis_info['status_msg'] == 'Running': sleep(0.05) redis_info = loads(r_client.get(loads(obs)['job_id'])) # Instantiate the artifact to make sure it was made and # to clean the environment a = Artifact(new_artifact_id) self._files_to_remove.extend([fp for _, fp, _ in a.filepaths]) # Test importing an artifact # Create new prep template to attach artifact to pt = npt.assert_warns(QiitaDBWarning, PrepTemplate.create, pd.DataFrame({'new_col': { '1.SKD6.640190': 1 }}), Study(1), '16S') self._files_to_remove.extend([fp for _, fp in pt.get_filepaths()]) new_artifact_id_2 = get_count('qiita.artifact') + 1 obs = artifact_post_req('*****@*****.**', {}, 'FASTQ', 'New Test Artifact 2', pt.id, new_artifact_id) exp = {'status': 'success', 'message': ''} self.assertEqual(obs, exp) obs = r_client.get('prep_template_%d' % pt.id) self.assertIsNotNone(obs) redis_info = loads(r_client.get(loads(obs)['job_id'])) while redis_info['status_msg'] == 'Running': sleep(0.05) redis_info = loads(r_client.get(loads(obs)['job_id'])) # Instantiate the artifact to make sure it was made and # to clean the environment a = Artifact(new_artifact_id_2) self._files_to_remove.extend([fp for _, fp, _ in a.filepaths])
def artifact_post_req(user, artifact_id): """Deletes the artifact Parameters ---------- user : qiita_db.user.User The user requesting the action artifact_id : int Id of the artifact being deleted """ artifact_id = int(artifact_id) artifact = Artifact(artifact_id) check_artifact_access(user, artifact) analysis = artifact.analysis if analysis: # Do something when deleting in the analysis part to keep track of it redis_key = "analysis_%s" % analysis.id else: pt_id = artifact.prep_templates[0].id redis_key = PREP_TEMPLATE_KEY_FORMAT % pt_id qiita_plugin = Software.from_name_and_version('Qiita', 'alpha') cmd = qiita_plugin.get_command('delete_artifact') params = Parameters.load(cmd, values_dict={'artifact': artifact_id}) job = ProcessingJob.create(user, params) r_client.set(redis_key, dumps({'job_id': job.id, 'is_qiita_job': True})) job.submit()
def artifact_post_req(user, artifact_id): """Deletes the artifact Parameters ---------- user : qiita_db.user.User The user requesting the action artifact_id : int Id of the artifact being deleted """ artifact_id = int(artifact_id) artifact = Artifact(artifact_id) check_artifact_access(user, artifact) analysis = artifact.analysis if analysis: # Do something when deleting in the analysis part to keep track of it redis_key = "analysis_%s" % analysis.id else: pt_id = artifact.prep_templates[0].id redis_key = PREP_TEMPLATE_KEY_FORMAT % pt_id job_id = safe_submit(user.id, delete_artifact, artifact_id) r_client.set(redis_key, dumps({'job_id': job_id, 'is_qiita_job': False}))
def artifact_delete_req(artifact_id, user_id): """Deletes the artifact Parameters ---------- artifact_id : int Artifact being acted on user_id : str The user requesting the action Returns ------- dict Status of action, in the form {'status': status, 'message': msg} status: status of the action, either success or error message: Human readable message for status """ pd = Artifact(int(artifact_id)) pt_id = pd.prep_templates[0].id access_error = check_access(pd.study.id, user_id) if access_error: return access_error job_id = safe_submit(user_id, delete_artifact, artifact_id) r_client.set(PREP_TEMPLATE_KEY_FORMAT % pt_id, dumps({'job_id': job_id})) return {'status': 'success', 'message': ''}
def post(self, preprocessed_data_id): user = self.current_user # make sure user is admin and can therefore actually submit to VAMPS if user.level != 'admin': raise HTTPError(403, "User %s cannot submit to VAMPS!" % user.id) msg = '' msg_level = 'success' study = Artifact(preprocessed_data_id).study study_id = study.id state = study.ebi_submission_status if state == 'submitting': msg = "Cannot resubmit! Current state is: %s" % state msg_level = 'danger' else: channel = user.id job_id = submit(channel, submit_to_VAMPS, int(preprocessed_data_id)) self.render( 'compute_wait.html', job_id=job_id, title='VAMPS Submission', completion_redirect=('/study/description/%s?top_tab=' 'preprocessed_data_tab&sub_tab=%s' % (study_id, preprocessed_data_id))) return self.display_template(preprocessed_data_id, msg, msg_level)
def copy_raw_data(prep_template, artifact_id): """Creates a new raw data by copying from artifact_id Parameters ---------- prep_template : qiita_db.metadata_template.prep_template.PrepTemplate The template to attach the artifact artifact_id : int The id of the artifact to duplicate Returns ------- dict of {str: str} A dict of the form {'status': str, 'message': str} """ from qiita_db.artifact import Artifact status = 'success' msg = '' try: Artifact.copy(Artifact(artifact_id), prep_template) except Exception as e: # We should hit this exception rarely (that's why it is an # exception) since at this point we have done multiple checks. # However, it can occur in weird cases, so better let the GUI know # that this failed return {'status': 'danger', 'message': "Error creating artifact: %s" % str(e)} return {'status': status, 'message': msg}
def setUp(self): uploads_path = get_mountpoint('uploads')[0][1] # Create prep test file to point at self.update_fp = join(uploads_path, '1', 'update.txt') with open(self.update_fp, 'w') as f: f.write("""sample_name\tnew_col\n1.SKD6.640190\tnew_value\n""") self._files_to_remove = [self.update_fp] self._files_to_remove = [] # creating temporal files and artifact # NOTE: we don't need to remove the artifact created cause it's # used to test the delete functionality fd, fp = mkstemp(suffix='_seqs.fna') close(fd) with open(fp, 'w') as f: f.write(">1.sid_r4_0 M02034:17:000000000-A5U18:1:1101:15370:1394 " "1:N:0:1 orig_bc=CATGAGCT new_bc=CATGAGCT bc_diffs=0\n" "GTGTGCCAGCAGCCGCGGTAATACGTAGGG\n") # 4 Demultiplexed filepaths_processed = [(fp, 4)] # 1 for default parameters and input data exp_params = Parameters.from_default_params(DefaultParameters(1), {'input_data': 1}) self.artifact = Artifact.create(filepaths_processed, "Demultiplexed", parents=[Artifact(1)], processing_parameters=exp_params)
def post(self, preprocessed_data_id): user = self.current_user # make sure user is admin and can therefore actually submit to VAMPS if user.level != 'admin': raise HTTPError(403, "User %s cannot submit to VAMPS!" % user.id) msg = '' msg_level = 'success' plugin = Software.from_name_and_version('Qiita', 'alpha') cmd = plugin.get_command('submit_to_VAMPS') artifact = Artifact(preprocessed_data_id) # Check if the artifact is already being submitted to VAMPS is_being_submitted = any([ j.status in ('queued', 'running') for j in artifact.jobs(cmd=cmd) ]) if is_being_submitted == 'submitting': msg = "Cannot resubmit! Data is already being submitted" msg_level = 'danger' self.display_template(preprocessed_data_id, msg, msg_level) else: params = Parameters.load( cmd, values_dict={'artifact': preprocessed_data_id}) job = ProcessingJob.create(user, params) job.submit() self.redirect('/study/description/%s' % artifact.study.study_id)
def get(self): artifact_id = self.get_argument("artifact_id", None) if artifact_id is None: raise HTTPError(422, reason='You need to specify an artifact id') else: try: artifact = Artifact(artifact_id) except QiitaDBUnknownIDError: raise HTTPError(404, reason='Artifact does not exist') else: if artifact.visibility != 'public': raise HTTPError(404, reason='Artifact is not public. If ' 'this is a mistake contact: ' '*****@*****.**') else: to_download = self._list_artifact_files_nginx(artifact) if not to_download: raise HTTPError(422, reason='Nothing to download. If ' 'this is a mistake contact: ' '*****@*****.**') else: self._write_nginx_file_list(to_download) zip_fn = 'artifact_%s_%s.zip' % ( artifact_id, datetime.now().strftime( '%m%d%y-%H%M%S')) self._set_nginx_headers(zip_fn) self.finish()
def approve_study(self, study, user, callback): """Approves the current study if and only if the current user is admin Parameters ---------- study : Study The current study object user : User The current user object callback : function The callback function to call with the results once the processing is done """ if _approve(user.level): pd_id = int(self.get_argument('pd_id')) pd = Artifact(pd_id) pd.visibility = 'private' _propagate_visibility(pd) msg = "Processed data approved" msg_level = "success" else: msg = ("The current user does not have permission to approve " "the processed data") msg_level = "danger" callback((msg, msg_level, "processed_data_tab", pd_id, None))
def test_download(self): # check failures response = self.get('/public_artifact_download/') self.assertEqual(response.code, 422) self.assertEqual(response.reason, 'You need to specify an artifact id') response = self.get('/public_artifact_download/?artifact_id=10000') self.assertEqual(response.code, 404) self.assertEqual(response.reason, 'Artifact does not exist') response = self.get('/public_artifact_download/?artifact_id=3') self.assertEqual(response.code, 404) self.assertEqual( response.reason, 'Artifact is not public. If this is ' 'a mistake contact: [email protected]') # check success Artifact(5).visibility = 'public' response = self.get('/public_artifact_download/?artifact_id=5') self.assertEqual(response.code, 200) exp = ('- [0-9]* /protected/processed_data/' '1_study_1001_closed_reference_otu_table.biom ' 'processed_data/1_study_1001_closed_reference_otu_table.biom\n' '- [0-9]* /protected/templates/1_prep_1_[0-9]*-[0-9]*.txt ' 'mapping_files/5_mapping_file.txt') self.assertRegex(response.body.decode('ascii'), exp)
def post(self, preprocessed_data_id): # make sure user is admin and can therefore actually submit to VAMPS if self.current_user.level != 'admin': raise HTTPError( 403, "User %s cannot submit to VAMPS!" % self.current_user.id) msg = '' msg_level = 'success' preprocessed_data = Artifact(preprocessed_data_id) state = preprocessed_data.submitted_to_vamps_status() demux = [ path for _, path, ftype in preprocessed_data.get_filepaths() if ftype == 'preprocessed_demux' ] demux_length = len(demux) if state in ('submitting', 'success'): msg = "Cannot resubmit! Current state is: %s" % state msg_level = 'danger' elif demux_length != 1: msg = "The study doesn't have demux files or have too many" % state msg_level = 'danger' else: channel = self.current_user.id job_id = submit(channel, submit_to_VAMPS, int(preprocessed_data_id)) self.render('compute_wait.html', job_id=job_id, title='VAMPS Submission', completion_redirect='/compute_complete/%s' % job_id) return self.display_template(preprocessed_data_id, msg, msg_level)
def get(self, analysis_id): analysis_id = int(analysis_id.split("/")[0]) analysis = Analysis(analysis_id) check_analysis_access(self.current_user, analysis) jobres = defaultdict(list) for jobject in analysis.jobs: results = [] for res in jobject.results: name = basename(res) if name.startswith('index'): name = basename(dirname(res)).replace('_', ' ') results.append((res, name)) jobres[jobject.datatype].append((jobject.command[0], results)) dropped_samples = analysis.dropped_samples dropped = defaultdict(list) for proc_data_id, samples in viewitems(dropped_samples): if not samples: continue proc_data = Artifact(proc_data_id) data_type = proc_data.data_type dropped[data_type].append( (proc_data.study.title, len(samples), ', '.join(samples))) self.render("analysis_results.html", analysis_id=analysis_id, jobres=jobres, aname=analysis.name, dropped=dropped, basefolder=get_db_files_base_dir())
def post(self, preprocessed_data_id): user = self.current_user # make sure user is admin and can therefore actually submit to EBI if user.level != 'admin': raise HTTPError(403, "User %s cannot submit to EBI!" % user.id) submission_type = self.get_argument('submission_type') if submission_type not in ['ADD', 'MODIFY']: raise HTTPError(403, "User: %s, %s is not a recognized submission " "type" % (user.id, submission_type)) msg = '' msg_level = 'success' study = Artifact(preprocessed_data_id).study study_id = study.id state = study.ebi_submission_status if state == 'submitting': msg = "Cannot resubmit! Current state is: %s" % state msg_level = 'danger' else: channel = user.id job_id = submit(channel, submit_to_ebi, int(preprocessed_data_id), submission_type) self.render('compute_wait.html', job_id=job_id, title='EBI Submission', completion_redirect=('/study/description/%s?top_tab=' 'preprocessed_data_tab&sub_tab=%s' % (study_id, preprocessed_data_id))) return self.display_template(preprocessed_data_id, msg, msg_level)
def get(self): # Format sel_data to get study IDs for the processed data sel_data = defaultdict(dict) proc_data_info = {} sel_samps = self.current_user.default_analysis.samples for pid, samps in viewitems(sel_samps): proc_data = Artifact(pid) sel_data[proc_data.study][pid] = samps # Also get processed data info parameters = proc_data.processing_parameters reference = Reference(parameters.values['reference']) proc_data_info[pid] = { 'processed_date': str(proc_data.timestamp), 'algorithm': parameters.command.name, 'reference_name': reference.name, 'reference_version': reference.version, 'sequence_filepath': reference.sequence_fp, 'taxonomy_filepath': reference.taxonomy_fp, 'tree_filepath': reference.tree_fp, 'data_type': proc_data.data_type } self.render("analysis_selected.html", sel_data=sel_data, proc_info=proc_data_info)
def test_artifact_post_req(self): # Create new prep template to attach artifact to pt = npt.assert_warns( QiitaDBWarning, PrepTemplate.create, pd.DataFrame({'new_col': {'1.SKD6.640190': 1}}), Study(1), '16S') self._files_to_remove.extend([fp for _, fp in pt.get_filepaths()]) filepaths = {'raw_forward_seqs': 'uploaded_file.txt', 'raw_barcodes': 'update.txt'} obs = artifact_post_req( '*****@*****.**', filepaths, 'FASTQ', 'New Test Artifact', pt.id) exp = {'status': 'success', 'message': ''} self.assertEqual(obs, exp) wait_for_prep_information_job(pt.id) # Test importing an artifact # Create new prep template to attach artifact to pt = npt.assert_warns( QiitaDBWarning, PrepTemplate.create, pd.DataFrame({'new_col': {'1.SKD6.640190': 1}}), Study(1), '16S') self._files_to_remove.extend([fp for _, fp in pt.get_filepaths()]) obs = artifact_post_req( '*****@*****.**', {}, 'Demultiplexed', 'New Test Artifact 2', pt.id, 3) exp = {'status': 'success', 'message': ''} self.assertEqual(obs, exp) wait_for_prep_information_job(pt.id) # Instantiate the artifact to make sure it was made and # to clean the environment a = Artifact(pt.artifact.id) self._files_to_remove.extend([fp for _, fp, _ in a.filepaths])
def get(self): # Format sel_data to get study IDs for the processed data sel_data = defaultdict(dict) proc_data_info = {} sel_samps = self.current_user.default_analysis.samples for aid, samples in viewitems(sel_samps): a = Artifact(aid) sel_data[a.study][aid] = samples # Also get processed data info processing_parameters = a.processing_parameters if processing_parameters is None: params = None algorithm = None else: cmd = processing_parameters.command params = processing_parameters.values if 'reference' in params: ref = Reference(params['reference']) del params['reference'] params['reference_name'] = ref.name params['reference_version'] = ref.version algorithm = '%s (%s)' % (cmd.software.name, cmd.name) proc_data_info[aid] = { 'processed_date': str(a.timestamp), 'algorithm': algorithm, 'data_type': a.data_type, 'params': params } self.render("analysis_selected.html", sel_data=sel_data, proc_info=proc_data_info)
def process_artifact_handler_get_req(artifact_id): """Returns the information for the process artifact handler Parameters ---------- artifact_id : int The artifact to be processed Returns ------- dict of str A dictionary containing the artifact information {'status': str, 'message': str, 'name': str, 'type': str} """ artifact = Artifact(artifact_id) return { 'status': 'success', 'message': '', 'name': artifact.name, 'type': artifact.artifact_type, 'study_id': artifact.study.id }
def test_artifact_patch_request(self): obs = artifact_patch_request('*****@*****.**', 'replace', '/%d/name/' % self.artifact.id, req_value='NEW_NAME') exp = {'status': 'success', 'message': ''} self.assertEqual(obs, exp) self.assertEqual(Artifact(self.artifact.id).name, 'NEW_NAME')
def artifact_graph_get_req(artifact_id, direction, user_id): """Creates graphs of ancestor or descendant artifacts from given one Parameters ---------- artifact_id : int Artifact ID to get graph for direction : {'ancestors', 'descendants'} What direction to get the graph in Returns ------- dict of lists of tuples A dictionary containing the edge list representation of the graph, and the node labels. Formatted as: {'status': status, 'message': message, 'edge_list': [(0, 1), (0, 2)...], 'node_labels': [(0, 'label0'), (1, 'label1'), ...]} Notes ----- Nodes are identified by the corresponding Artifact ID. """ access_error = check_access(Artifact(artifact_id).study.id, user_id) if access_error: return access_error if direction == 'descendants': G = Artifact(int(artifact_id)).descendants elif direction == 'ancestors': G = Artifact(int(artifact_id)).ancestors else: return { 'status': 'error', 'message': 'Unknown directon %s' % direction } node_labels = [(n.id, ' - '.join([n.name, n.artifact_type])) for n in G.nodes()] return { 'edge_list': [(n.id, m.id) for n, m in G.edges()], 'node_labels': node_labels, 'status': 'success', 'message': '' }
def test_download(self): # check success response = self.get('/download/1') self.assertEqual(response.code, 200) self.assertEqual(response.body.decode('ascii'), ( "This installation of Qiita was not equipped with nginx, so it " "is incapable of serving files. The file you attempted to " "download is located at raw_data/1_s_G1_L001_sequences.fastq.gz")) self.assertEqual( response.headers['Content-Disposition'], "attachment; filename=1_1_s_G1_L001_sequences.fastq.gz") # other tests to validate the filename response = self.get('/download/2') self.assertEqual( response.headers['Content-Disposition'], "attachment; filename=1_1_s_G1_L001_sequences_barcodes.fastq.gz") response = self.get('/download/3') self.assertEqual( response.headers['Content-Disposition'], "attachment; filename=2_1_seqs.fna") response = self.get('/download/18') self.assertEqual( response.headers['Content-Disposition'], "attachment; filename=1_prep_1_19700101-000000.txt") response = self.get('/download/22') self.assertEqual( response.headers['Content-Disposition'], "attachment; filename=7_biom_table.biom") # failure response = self.get('/download/1000') self.assertEqual(response.code, 403) # directory a = Artifact(1) fd, fp = mkstemp(suffix='.html') close(fd) with open(fp, 'w') as f: f.write('\n') self._clean_up_files.append(fp) dirpath = mkdtemp() fd, fp2 = mkstemp(suffix='.txt', dir=dirpath) close(fd) with open(fp2, 'w') as f: f.write('\n') self._clean_up_files.append(dirpath) a.set_html_summary(fp, support_dir=dirpath) for x in a.filepaths: if x['fp_type'] == 'html_summary_dir': break response = self.get('/download/%d' % x['fp_id']) self.assertEqual(response.code, 200) fp_name = basename(fp2) dirname = basename(dirpath) self.assertEqual(response.body.decode('ascii'), "- 1 /protected/FASTQ/1/%s/%s FASTQ/1/%s/%s\n" % ( dirname, fp_name, dirname, fp_name))
def prep_template_graph_get_req(prep_id, user_id): """Returns graph of all artifacts created from the prep base artifact Parameters ---------- prep_id : int Prep template ID to get graph for user_id : str User making the request Returns ------- dict of lists of tuples A dictionary containing the edge list representation of the graph, and the node labels. Formatted as: {'status': status, 'message': message, 'edge_list': [(0, 1), (0, 2)...], 'node_labels': [(0, 'label0'), (1, 'label1'), ...]} Notes ----- Nodes are identified by the corresponding Artifact ID. """ exists = _check_prep_template_exists(int(prep_id)) if exists['status'] != 'success': return exists prep = PrepTemplate(int(prep_id)) access_error = check_access(prep.study_id, user_id) if access_error: return access_error # We should filter for only the public artifacts if the user # doesn't have full access to the study full_access = Study(prep.study_id).can_edit(User(user_id)) artifact = prep.artifact if artifact is None: return {'edges': [], 'nodes': [], 'status': 'success', 'message': ''} G = artifact.descendants_with_jobs nodes, edges, wf_id = get_network_nodes_edges(G, full_access) # nodes returns [node_type, node_name, element_id]; here we are looking # for the node_type == artifact, and check by the element/artifact_id if # it's being deleted artifacts_being_deleted = [a[2] for a in nodes if a[0] == 'artifact' and Artifact(a[2]).being_deleted_by is not None] return {'edges': edges, 'nodes': nodes, 'workflow': wf_id, 'status': 'success', 'artifacts_being_deleted': artifacts_being_deleted, 'message': ''}
def test_public(self): response = self.get('/public/') self.assertEqual(response.code, 422) self.assertIn("You need to specify study_id or artifact_id", response.body.decode('ascii')) response = self.get('/public/?study_id=100') self.assertEqual(response.code, 422) self.assertIn("Study 100 doesn't exist", response.body.decode('ascii')) response = self.get('/public/?artifact_id=100') self.assertEqual(response.code, 422) self.assertIn("Artifact 100 doesn't exist", response.body.decode('ascii')) response = self.get('/public/?artifact_id=1') self.assertEqual(response.code, 422) self.assertIn("Artifact 1 is not public", response.body.decode('ascii')) response = self.get('/public/?study_id=1') self.assertEqual(response.code, 422) self.assertIn("Not a public study", response.body.decode('ascii')) # artifact 1 is the first artifact within Study 1 Artifact(1).visibility = 'public' response = self.get('/public/?study_id=1') self.assertEqual(response.code, 200) response = self.get('/public/?artifact_id=1') self.assertEqual(response.code, 200) response = self.get('/public/?artifact_id=7') self.assertEqual(response.code, 422) self.assertIn("Artifact 7 is not public", response.body.decode('ascii')) # artifact 8 is part of an analysis Artifact(8).visibility = 'public' response = self.get('/public/?artifact_id=8') self.assertEqual(response.code, 422) self.assertIn("Artifact 8 doesn't belong to a study", response.body.decode('ascii'))