def test_upload_with_validation_errors(self): request_files = ImmutableMultiDict({'pricingDocumentURL': mock_file('q1.bad', 100)}) files, errors = upload_service_documents( 'bucket', self.documents_url, self.service, request_files, self.section) assert files is None assert 'pricingDocumentURL' in errors
def test_only_files_in_section_are_uploaded(self): request_files = ImmutableMultiDict({'serviceDefinitionDocumentURL': mock_file('q1.pdf', 100)}) files, errors = upload_service_documents( 'bucket', self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_empty_files_are_filtered(self): request_files = {'pricingDocumentURL': mock_file('q1.pdf', 0)} files, errors = upload_service_documents( self.uploader, self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_upload_with_validation_errors(self): request_files = {'pricingDocumentURL': MockFile(b"*" * 100, 'q1.bad')} files, errors = upload_service_documents( self.uploader, 'documents', self.documents_url, self.service, request_files, self.section) assert files is None assert 'pricingDocumentURL' in errors
def test_only_files_in_section_are_uploaded(self): request_files = {'serviceDefinitionDocumentURL': mock_file('q1.pdf', 100)} files, errors = upload_service_documents( self.uploader, self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_empty_files_are_filtered(self): request_files = {'pricingDocumentURL': MockFile(b"", 'q1.pdf')} files, errors = upload_service_documents( self.uploader, 'documents', self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_only_files_in_section_are_uploaded(self): request_files = {'serviceDefinitionDocumentURL': MockFile(b"*" * 100, 'q1.pdf')} files, errors = upload_service_documents( self.uploader, 'documents', self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_empty_files_are_filtered(self): request_files = ImmutableMultiDict({'pricingDocumentURL': mock_file('q1.pdf', 0)}) files, errors = upload_service_documents( 'bucket', self.documents_url, self.service, request_files, self.section) assert len(files) == 0 assert len(errors) == 0
def test_upload_with_validation_errors(self): request_files = {'pricingDocumentURL': mock_file('q1.bad', 100)} files, errors = upload_service_documents( self.uploader, self.documents_url, self.service, request_files, self.section) assert files is None assert 'pricingDocumentURL' in errors
def update_service(service_id, section_id, question_slug=None): service = data_api_client.get_service(service_id) if service is None: abort(404) service = service['services'] # we don't actually need the framework here; using this to 404 if framework for the service is not live # TODO remove `expired` from below. It's a temporary fix to allow access to DOS2 as it's expired. get_framework_or_404(data_api_client, service['frameworkSlug'], allowed_statuses=['live', 'expired']) content = content_loader.get_manifest( service['frameworkSlug'], 'edit_service_as_admin', ).filter(service, inplace_allowed=True) section = content.get_section(section_id) if question_slug is not None: # Overwrite section with single question section for 'question per page' editing. section = section.get_question_as_section(question_slug) if section is None or not section.editable: abort(404) errors = None posted_data = section.get_data(request.form) uploaded_documents, document_errors = upload_service_documents( s3.S3(current_app.config['DM_S3_DOCUMENT_BUCKET']), 'documents', current_app.config['DM_ASSETS_URL'], service, request.files, section) if document_errors: errors = section.get_error_messages(document_errors) else: posted_data.update(uploaded_documents) if not errors and section.has_changes_to_save(service, posted_data): try: data_api_client.update_service( service_id, posted_data, current_user.email_address, user_role='admin', ) except HTTPError as e: errors = section.get_error_messages(e.message) if errors: return render_template( "edit_section.html", section=section, service_data=section.unformat_data(dict(service, **posted_data)), service_id=service_id, errors=govuk_errors(errors), ), 400 return redirect(url_for(".view_service", service_id=service_id))
def test_upload_service_documents(self): request_files = ImmutableMultiDict({'pricingDocumentURL': mock_file('q1.pdf', 100)}) with freeze_time('2015-10-04 14:36:05'): with patch('boto3.s3.inject.bucket_upload_fileobj'): files, errors = upload_service_documents( 'bucket', self.documents_url, self.service, request_files, self.section) assert 'pricingDocumentURL' in files assert len(errors) == 0
def test_upload_service_documents(self): request_files = {'pricingDocumentURL': mock_file('q1.pdf', 100)} with freeze_time('2015-10-04 14:36:05'): files, errors = upload_service_documents( self.uploader, self.documents_url, self.service, request_files, self.section) self.uploader.save.assert_called_with( 'g-cloud-7/documents/12345/654321-pricing-document-2015-10-04-1436.pdf', mock.ANY, acl='public-read') assert 'pricingDocumentURL' in files assert len(errors) == 0
def test_upload_service_documents(self): request_files = {'pricingDocumentURL': self.test_pdf} with freeze_time('2015-10-04 14:36:05'): files, errors = upload_service_documents( self.uploader, 'documents', self.documents_url, self.service, request_files, self.section) self.uploader.save.assert_called_with( 'g-cloud-7/documents/12345/654321-pricing-document-2015-10-04-1436.pdf', mock.ANY, acl='public-read') assert 'pricingDocumentURL' in files assert len(errors) == 0
def update(service_id, section_id): service = data_api_client.get_service(service_id) if service is None: abort(404) service = service['services'] content = content_loader.get_builder('g-cloud-6', 'edit_service_as_admin').filter(service) section = content.get_section(section_id) if section is None or not section.editable: abort(404) errors = None posted_data = section.get_data(request.form) uploaded_documents, document_errors = upload_service_documents( S3(current_app.config['DM_S3_DOCUMENT_BUCKET']), current_app.config['DM_DOCUMENTS_URL'], service, request.files, section) if document_errors: errors = section.get_error_messages(document_errors, service['lot']) else: posted_data.update(uploaded_documents) if not errors and section.has_changes_to_save(service, posted_data): try: data_api_client.update_service( service_id, posted_data, current_user.email_address) except HTTPError as e: errors = section.get_error_messages(e.message, service['lot']) if errors: if not posted_data.get('serviceName', None): posted_data['serviceName'] = service.get('serviceName', '') return render_template( "edit_section.html", **get_template_data( section=section, service_data=posted_data, service_id=service_id, errors=errors ) ), 400 return redirect(url_for(".view", service_id=service_id))
def update(service_id, section_id): service = data_api_client.get_service(service_id) if service is None: abort(404) service = service['services'] content = content_loader.get_builder('g-cloud-6', 'edit_service_as_admin').filter(service) section = content.get_section(section_id) if section is None or not section.editable: abort(404) errors = None posted_data = section.get_data(request.form) uploaded_documents, document_errors = upload_service_documents( S3(current_app.config['DM_S3_DOCUMENT_BUCKET']), current_app.config['DM_DOCUMENTS_URL'], service, request.files, section) if document_errors: errors = section.get_error_messages(document_errors) else: posted_data.update(uploaded_documents) if not errors and section.has_changes_to_save(service, posted_data): try: data_api_client.update_service( service_id, posted_data, current_user.email_address) except HTTPError as e: errors = section.get_error_messages(e.message) if errors: if not posted_data.get('serviceName', None): posted_data['serviceName'] = service.get('serviceName', '') return render_template( "edit_section.html", section=section, service_data=posted_data, service_id=service_id, errors=errors ), 400 return redirect(url_for(".view", service_id=service_id))
def test_upload_private_service_documents(self): request_files = {'pricingDocumentURL': MockFile(b"*" * 100, 'q1.pdf')} with freeze_time('2015-10-04 14:36:05'): files, errors = upload_service_documents(self.uploader, 'documents', self.documents_url, self.service, request_files, self.section, public=False) self.uploader.save.assert_called_with( 'g-cloud-7/documents/12345/654321-pricing-document-2015-10-04-1436.pdf', mock.ANY, acl='bucket-owner-full-control') assert 'pricingDocumentURL' in files assert len(errors) == 0
def update_section(framework_slug, service_id, section_id): service = data_api_client.get_service(service_id) if service is None: abort(404) service = service['services'] if not is_service_associated_with_supplier(service): abort(404) if service["frameworkSlug"] != framework_slug: abort(404) # we don't actually need the framework here; using this to 404 if framework for the service is not live get_framework_or_404(data_api_client, service['frameworkSlug'], allowed_statuses=['live']) try: content = content_loader.get_manifest(service["frameworkSlug"], 'edit_service').filter( service, inplace_allowed=True, ) except ContentNotFoundError: abort(404) section = content.get_section(section_id) if section is None or not section.editable: abort(404) posted_data = section.get_data(request.form) errors = None # This utils method filters out any empty documents and validates against service document rules uploaded_documents, document_errors = upload_service_documents( s3.S3(current_app.config['DM_DOCUMENTS_BUCKET']), 'documents', current_app.config['DM_ASSETS_URL'], service, request.files, section, ) if document_errors: errors = section.get_error_messages(document_errors) else: posted_data.update(uploaded_documents) if not errors and section.has_changes_to_save(service, posted_data): try: data_api_client.update_service( service_id, posted_data, current_user.email_address) except HTTPError as e: errors = section.get_error_messages(e.message) if errors: session_timeout = displaytimeformat(datetime.utcnow() + timedelta(hours=1)) return render_template( "services/edit_section.html", section=section, service_data=service, service_id=service_id, session_timeout=session_timeout, errors=errors, ), 400 flash(SERVICE_UPDATED_MESSAGE, "success") return redirect(url_for(".edit_service", service_id=service_id, framework_slug=service["frameworkSlug"]))
def submit_brief_response(brief_id): """Hits up the data API to create a new brief response.""" brief = get_brief(data_api_client, brief_id) if brief['status'] != 'live': return render_template( "briefs/brief_closed_error.html" ), 400 if not is_supplier_selected_for_brief(data_api_client, current_user, brief): return _render_not_selected_for_brief_error_page() ineligible = is_supplier_not_eligible_for_brief(data_api_client, current_user.supplier_code, brief) if ineligible: return _render_error_page(ineligible, brief, clarification_question=True) if supplier_has_a_brief_response(data_api_client, current_user.supplier_code, brief_id): flash('already_applied', 'error') return redirect(url_for(".view_response_result", brief_id=brief_id)) framework, lot = get_framework_and_lot( data_api_client, brief['frameworkSlug'], brief['lotSlug'], allowed_statuses=['live']) content = content_loader.get_manifest(framework['slug'], 'edit_brief_response').filter({'lot': lot['slug']}) section = content.get_section(content.get_next_editable_section_id()) response_data = section.get_data(request.form) service = {'frameworkSlug': brief['frameworkSlug'], 'supplierCode': current_user.supplier_code, 'id': brief_id} uploaded_documents, document_errors = upload_service_documents(current_app.config.get('S3_BUCKET_NAME'), "", service, request.files, section) if document_errors: # replace generic 'Apply for opportunity' title with title including the name of the brief section.name = "Apply for ‘{}’".format(brief['title']) section.inject_brief_questions_into_boolean_list_question(brief) section_summary = section.summary(response_data) errors = section_summary.get_error_messages(document_errors) if errors.get('attachedDocumentURL', {}).get('question'): errors['attachedDocumentURL']['question'] = "Attached document" return render_template_with_csrf( "briefs/brief_response.html", status_code=400, brief=brief, service_data=response_data, section=section, errors=errors, ) else: response_data.update(uploaded_documents) try: brief_response = data_api_client.create_brief_response( brief_id, current_user.supplier_code, response_data, current_user.email_address )['briefResponses'] except HTTPError as e: # replace generic 'Apply for opportunity' title with title including the name of the brief section.name = "Apply for ‘{}’".format(brief['title']) section.inject_brief_questions_into_boolean_list_question(brief) section_summary = section.summary(response_data) errors = {} if isinstance(e.message, dict): errors = section_summary.get_error_messages(e.message) rollbar.report_message(json.dumps(errors), 'error', request) else: flash('already_applied', 'error') return render_template_with_csrf( "briefs/brief_response.html", status_code=400, brief=brief, service_data=response_data, section=section, errors=errors, ) response_url = url_for(".view_response_result", brief_id=brief_id, result='success') send_thank_you_email_to_responders(brief, brief_response, response_url) return redirect(response_url)
def update_section_submission(framework_slug, lot_slug, service_id, section_id, question_slug=None): framework, lot = get_framework_and_lot(data_api_client, framework_slug, lot_slug, allowed_statuses=['open']) try: draft = data_api_client.get_draft_service(service_id)['services'] except HTTPError as e: abort(e.status_code) if draft['lotSlug'] != lot_slug or draft['frameworkSlug'] != framework_slug: abort(404) if not is_service_associated_with_supplier(draft): abort(404) content = content_loader.get_manifest(framework_slug, 'edit_submission').filter(draft) section = content.get_section(section_id) if section and (question_slug is not None): section = section.get_question_as_section(question_slug) if section is None or not section.editable: abort(404) errors = None document_errors = None update_data = section.get_data(request.form) uploader = s3.S3(current_app.config['DM_SUBMISSIONS_BUCKET']) documents_url = url_for('.dashboard', _external=True) + '/assets/' uploaded_documents, document_errors = upload_service_documents( uploader, documents_url, draft, request.files, section, public=False) if document_errors: errors = section.get_error_messages(document_errors) else: update_data.update(uploaded_documents) if not errors and section.has_changes_to_save(draft, update_data): try: data_api_client.update_draft_service( service_id, update_data, current_user.email_address, page_questions=section.get_field_names()) except HTTPError as e: update_data = section.unformat_data(update_data) errors = section.get_error_messages(e.message) if errors: keys_required_for_template = ['serviceName', 'lot', 'lotName'] for k in keys_required_for_template: if k in draft and k not in update_data: update_data[k] = draft[k] return render_template_with_csrf( "services/edit_submission_section.html", framework=framework, section=section, next_section_name=get_next_section_name(content, section.id), service_data=update_data, service_id=service_id, one_service_limit=lot['oneServiceLimit'], return_to_summary=bool(request.args.get('return_to_summary')), errors=errors) return_to_summary = bool(request.args.get('return_to_summary')) next_section = content.get_next_editable_section_id(section_id) if next_section and not return_to_summary and request.form.get( 'continue_to_next_section'): return redirect( url_for(".edit_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, section_id=next_section)) else: return redirect( url_for(".view_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, _anchor=section_id))
def update_section_submission(framework_slug, lot_slug, service_id, section_id, question_slug=None): framework, lot = get_framework_and_lot(data_api_client, framework_slug, lot_slug, allowed_statuses=['open']) try: draft = data_api_client.get_draft_service(service_id)['services'] except HTTPError as e: abort(e.status_code) if draft['lotSlug'] != lot_slug or draft['frameworkSlug'] != framework_slug: abort(404) if not is_service_associated_with_supplier(draft): abort(404) content = content_loader.get_manifest(framework_slug, 'edit_submission').filter(draft) section = content.get_section(section_id) if section and (question_slug is not None): section = section.get_question_as_section(question_slug) if section is None or not section.editable: abort(404) errors = None document_errors = None update_data = section.get_data(request.form) uploader = s3.S3(current_app.config['DM_SUBMISSIONS_BUCKET']) documents_url = url_for('.dashboard', _external=True) + '/assets/' uploaded_documents, document_errors = upload_service_documents( uploader, documents_url, draft, request.files, section, public=False) if document_errors: errors = section.get_error_messages(document_errors) else: update_data.update(uploaded_documents) if not errors and section.has_changes_to_save(draft, update_data): try: data_api_client.update_draft_service( service_id, update_data, current_user.email_address, page_questions=section.get_field_names() ) except HTTPError as e: update_data = section.unformat_data(update_data) errors = section.get_error_messages(e.message) if errors: keys_required_for_template = ['serviceName', 'lot', 'lotName'] for k in keys_required_for_template: if k in draft and k not in update_data: update_data[k] = draft[k] return render_template_with_csrf( "services/edit_submission_section.html", framework=framework, section=section, next_section_name=get_next_section_name(content, section.id), service_data=update_data, service_id=service_id, one_service_limit=lot['oneServiceLimit'], return_to_summary=bool(request.args.get('return_to_summary')), errors=errors ) return_to_summary = bool(request.args.get('return_to_summary')) next_section = content.get_next_editable_section_id(section_id) if next_section and not return_to_summary and request.form.get('continue_to_next_section'): return redirect(url_for(".edit_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, section_id=next_section)) else: return redirect(url_for(".view_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, _anchor=section_id))
def edit_service_submission(framework_slug, lot_slug, service_id, section_id, question_slug=None): """ Also accepts URL parameter `force_continue_button` which will allow rendering of a 'Save and continue' button, used for when copying services. """ framework, lot = get_framework_and_lot_or_404(data_api_client, framework_slug, lot_slug, allowed_statuses=['open']) # Suppliers must have registered interest in a framework before they can edit draft services if not get_supplier_framework_info(data_api_client, framework_slug): abort(404) force_return_to_summary = framework['framework'] == "digital-outcomes-and-specialists" force_continue_button = request.args.get('force_continue_button') next_question = None try: draft = data_api_client.get_draft_service(service_id)['services'] except HTTPError as e: abort(e.status_code) if draft['lotSlug'] != lot_slug or draft['frameworkSlug'] != framework_slug: abort(404) if not is_service_associated_with_supplier(draft): abort(404) content = content_loader.get_manifest(framework_slug, 'edit_submission').filter(draft, inplace_allowed=True) section = content.get_section(section_id) if section and (question_slug is not None): next_question = section.get_question_by_slug(section.get_next_question_slug(question_slug)) section = section.get_question_as_section(question_slug) if section is None or not section.editable: abort(404) errors = None if request.method == "POST": update_data = section.get_data(request.form) if request.files: uploader = s3.S3(current_app.config['DM_SUBMISSIONS_BUCKET']) documents_url = url_for('.dashboard', _external=True) + '/assets/' # This utils method filters out any empty documents and validates against service document rules uploaded_documents, document_errors = upload_service_documents( uploader, 'submissions', documents_url, draft, request.files, section, public=False) if document_errors: errors = section.get_error_messages(document_errors, question_descriptor_from="question") else: update_data.update(uploaded_documents) if not errors and section.has_changes_to_save(draft, update_data): try: data_api_client.update_draft_service( service_id, update_data, current_user.email_address, page_questions=section.get_field_names() ) except HTTPError as e: update_data = section.unformat_data(update_data) errors = section.get_error_messages(e.message, question_descriptor_from="question") if not errors: if next_question and not force_return_to_summary: return redirect(url_for(".edit_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, section_id=section_id, question_slug=next_question.slug)) else: return redirect(url_for(".view_service_submission", framework_slug=framework['slug'], lot_slug=draft['lotSlug'], service_id=service_id, _anchor=section_id)) update_data.update( (k, draft[k]) for k in ('serviceName', 'lot', 'lotName',) if k in draft and k not in update_data ) service_data = update_data # fall through to regular GET path to display errors else: service_data = section.unformat_data(draft) session_timeout = displaytimeformat(datetime.utcnow() + timedelta(hours=1)) return render_template( "services/edit_submission_section.html", section=section, framework=framework, lot=lot, next_question=next_question, service_data=service_data, service_id=service_id, force_return_to_summary=force_return_to_summary, force_continue_button=force_continue_button, session_timeout=session_timeout, errors=errors, )