def list_project_searches(project_external_id): project = get_project_by_id_or_404(project_external_id) page = get_valid_page_or_1() searches = DirectAwardSearch.query.filter(DirectAwardSearch.project_id == project.id) if 'latest-first' in request.args: if convert_to_boolean(request.args.get('latest-first')): searches = searches.order_by(desc(DirectAwardSearch.created_at), desc(DirectAwardSearch.id)) else: searches = searches.order_by(asc(DirectAwardSearch.created_at), asc(DirectAwardSearch.id)) else: searches = searches.order_by(asc(DirectAwardSearch.id)) if convert_to_boolean(request.args.get('only-active', False)): searches = searches.filter(DirectAwardSearch.active == True) # noqa pagination_params = request.args.to_dict() pagination_params['project_external_id'] = project.external_id return paginated_result_response( result_name="searches", results_query=searches, page=page, per_page=current_app.config['DM_API_PROJECTS_PAGE_SIZE'], endpoint='.list_project_searches', request_args=pagination_params, ), 200
def list_projects(): page = get_valid_page_or_1() projects = DirectAwardProject.query.options( db.joinedload( DirectAwardProject.outcome ).joinedload( Outcome.direct_award_archived_service ).load_only( "service_id" ) ) includes = request.args.get('include', '').split(',') with_users = 'users' in includes if with_users: projects = projects.options(db.joinedload('users').lazyload('supplier')) user_id = get_int_or_400(request.args, 'user-id') if user_id: projects = projects.filter(DirectAwardProject.users.any(id=user_id)) if "having-outcome" in request.args: projects = projects.filter( db.cast( DirectAwardProject.outcome.has(), db.Boolean, ) == convert_to_boolean(request.args.get("having-outcome")) ) if "locked" in request.args: projects = projects.filter( db.cast( DirectAwardProject.locked_at.isnot(None), db.Boolean, ) == convert_to_boolean(request.args.get("locked")) ) if 'latest-first' in request.args: if convert_to_boolean(request.args.get('latest-first')): projects = projects.order_by(desc(DirectAwardProject.created_at), desc(DirectAwardProject.id)) else: projects = projects.order_by(asc(DirectAwardProject.created_at), asc(DirectAwardProject.id)) else: projects = projects.order_by(asc(DirectAwardProject.id)) return paginated_result_response( result_name="projects", results_query=projects, page=page, per_page=current_app.config['DM_API_PROJECTS_PAGE_SIZE'], endpoint='.list_projects', request_args=request.args, serialize_kwargs={"with_users": with_users} ), 200
def list_audits(): page = get_valid_page_or_1() try: per_page = int( request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') audits = AuditEvent.query.order_by(asc(AuditEvent.created_at)) audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audits = audits.filter( cast(AuditEvent.created_at, Date) == audit_date) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: if AuditTypes.is_valid_audit_type(audit_type): audits = audits.filter(AuditEvent.type == audit_type) else: abort(400, "Invalid audit type") acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter(AuditEvent.acknowledged == true()) elif not convert_to_boolean(acknowledged): audits = audits.filter(AuditEvent.acknowledged == false()) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') if not object_id: abort(400, 'object-type cannot be provided without object-id') model = AUDIT_OBJECT_TYPES[object_type] id_field = AUDIT_OBJECT_ID_FIELDS[object_type] audits = audits.join(model, model.id == AuditEvent.object_id) \ .filter(id_field == object_id) elif object_id: abort(400, 'object-id cannot be provided without object-type') audits = audits.paginate(page=page, per_page=per_page) return jsonify(auditEvents=[audit.serialize() for audit in audits.items], links=pagination_links(audits, '.list_audits', request.args))
def get_framework_suppliers(framework_slug): framework = Framework.query.filter( Framework.slug == framework_slug ).first_or_404() # we're going to need an "alias" for the current_framework_agreement join # so that we can refer to it in later clauses cfa = orm.aliased(SupplierFramework._CurrentFrameworkAgreement) supplier_frameworks = SupplierFramework.query.outerjoin( cfa, SupplierFramework.current_framework_agreement ).options( orm.contains_eager( SupplierFramework.current_framework_agreement, alias=cfa ) ) # now we can use the alias `cfa` to refer to the joinedload that # SupplierFramework.current_framework_agreement would usually cause supplier_frameworks = supplier_frameworks.filter( SupplierFramework.framework_id == framework.id ).options( db.defaultload(SupplierFramework.framework).lazyload("*"), db.defaultload(SupplierFramework.supplier).lazyload("*"), db.defaultload(SupplierFramework.prefill_declaration_from_framework).lazyload("*"), db.lazyload(SupplierFramework.framework_agreements), ).order_by( # Listing agreements is something done for Admin only (suppliers only retrieve their individual agreements) # and CCS always want to work from the oldest returned date to newest, so order by ascending date cfa.signed_agreement_returned_at.asc().nullsfirst(), SupplierFramework.supplier_id, ) # endpoint has evolved a bit of an oddly designed interface here in that the two filterable parameters aren't # really orthogonal. implementing them as such anyway for clarity. agreement_returned = request.args.get('agreement_returned') if agreement_returned is not None: supplier_frameworks = supplier_frameworks.filter( # using the not-nullable FrameworkAgreement.id as a proxy for testing row null-ness cfa.id.isnot(None) if convert_to_boolean(agreement_returned) else cfa.id.is_(None) ) status = request.args.get('status') if status is not None: supplier_frameworks = supplier_frameworks.filter( cfa.status.in_(status.split(",")) ) with_declarations = convert_to_boolean(request.args.get("with_declarations", "true")) return list_result_response( "supplierFrameworks", supplier_frameworks, serialize_kwargs={"with_users": False, "with_declaration": with_declarations} ), 200
def list_audits(): try: page = int(request.args.get('page', 1)) except ValueError: abort(400, "Invalid page argument") audits = AuditEvent.query.order_by( asc(AuditEvent.created_at) ) audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audits = audits.filter( cast(AuditEvent.created_at, Date) == audit_date ) else: abort(400, 'invalid audit date supplied') if request.args.get('audit-type'): if AuditTypes.is_valid_audit_type(request.args.get('audit-type')): audits = audits.filter( AuditEvent.type == request.args.get('audit-type') ) else: abort(400, "Invalid audit type") acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == true() ) elif not convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == false() ) else: abort(400, 'invalid acknowledged state supplied') audits = audits.paginate( page=page, per_page=current_app.config['DM_API_SERVICES_PAGE_SIZE'], ) return jsonify( auditEvents=[audit.serialize() for audit in audits.items], links=pagination_links( audits, '.list_audits', request.args ) )
def list_users(): user_query = User.query.order_by(User.id) page = get_valid_page_or_1() # email_address is a primary key email_address = request.args.get('email_address') if email_address: if not is_valid_email_address(email_address): abort(400, "email_address must be a valid email address") single_user = user_query.filter( User.email_address == email_address.lower()).first_or_404() return jsonify(users=[single_user.serialize()]) role = request.args.get('role') if role: if role in User.ROLES: user_query = user_query.filter(User.role == role) else: abort(400, 'Invalid user role: {}'.format(role)) supplier_id = request.args.get('supplier_id') if supplier_id is not None: try: supplier_id = int(supplier_id) except ValueError: abort(400, "Invalid supplier_id: {}".format(supplier_id)) supplier = Supplier.query.filter( Supplier.supplier_id == supplier_id).all() if not supplier: abort(404, "supplier_id '{}' not found".format(supplier_id)) user_query = user_query.filter(User.supplier_id == supplier_id) personal_data_removed = request.args.get('personal_data_removed') if personal_data_removed is not None: user_query = user_query.filter( User.personal_data_removed == convert_to_boolean( personal_data_removed)) user_research_opted_in = request.args.get('user_research_opted_in') if user_research_opted_in is not None: user_query = user_query.filter( User.user_research_opted_in == convert_to_boolean( user_research_opted_in)) return paginated_result_response( result_name=RESOURCE_NAME, results_query=user_query, page=page, per_page=current_app.config['DM_API_SERVICES_PAGE_SIZE'], endpoint='.list_users', request_args=request.args), 200
def list_countersigned_agreement_file(supplier_id, framework_slug): supplier = data_api_client.get_supplier(supplier_id)['suppliers'] framework = data_api_client.get_framework(framework_slug)['frameworks'] supplier_framework = data_api_client.get_supplier_framework_info(supplier_id, framework_slug)['frameworkInterest'] if not supplier_framework['onFramework'] or supplier_framework['agreementStatus'] in (None, 'draft'): abort(404) agreements_bucket = s3.S3( current_app.config['DM_AGREEMENTS_BUCKET'], endpoint_url=current_app.config.get("DM_S3_ENDPOINT_URL") ) countersigned_agreement_document = agreements_bucket.get_key(supplier_framework.get('countersignedPath')) remove_countersigned_agreement_confirm = convert_to_boolean(request.args.get('remove_countersigned_agreement')) countersigned_agreement = [] if countersigned_agreement_document: last_modified = datetimeformat(parse_date(countersigned_agreement_document['last_modified'])) document_name = degenerate_document_path_and_return_doc_name(supplier_framework.get('countersignedPath')) countersigned_agreement = [{"last_modified": last_modified, "document_name": document_name}] return render_template( "suppliers/upload_countersigned_agreement.html", supplier=supplier, framework=framework, countersigned_agreement=countersigned_agreement, remove_countersigned_agreement_confirm=remove_countersigned_agreement_confirm )
def list_projects(): page = get_valid_page_or_1() projects = DirectAwardProject.query includes = request.args.get('include', '').split(',') with_users = 'users' in includes if with_users: projects = projects.options( db.joinedload('users').lazyload('supplier')) user_id = get_int_or_400(request.args, 'user-id') if user_id: projects = projects.filter(DirectAwardProject.users.any(id=user_id)) if 'latest-first' in request.args: if convert_to_boolean(request.args.get('latest-first')): projects = projects.order_by(desc(DirectAwardProject.created_at), desc(DirectAwardProject.id)) else: projects = projects.order_by(asc(DirectAwardProject.created_at), asc(DirectAwardProject.id)) else: projects = projects.order_by(asc(DirectAwardProject.id)) return paginated_result_response( result_name="projects", results_query=projects, page=page, per_page=current_app.config['DM_API_PROJECTS_PAGE_SIZE'], endpoint='.list_projects', request_args=request.args, serialize_kwargs={"with_users": with_users}), 200
def list_brief_responses(): page = get_valid_page_or_1() brief_id = get_int_or_400(request.args, 'brief_id') supplier_id = get_int_or_400(request.args, 'supplier_id') awarded_at = request.args.get('awarded_at') with_data = convert_to_boolean(request.args.get("with-data", "true")) if request.args.get('status'): statuses = request.args['status'].split(',') else: statuses = COMPLETED_BRIEF_RESPONSE_STATUSES brief_responses = BriefResponse.query.filter(BriefResponse.status.in_(statuses)) if supplier_id is not None: brief_responses = brief_responses.filter(BriefResponse.supplier_id == supplier_id) if brief_id is not None: brief_responses = brief_responses.filter(BriefResponse.brief_id == brief_id) if awarded_at is not None: day_start = datetime.strptime(awarded_at, DATE_FORMAT) day_end = datetime(day_start.year, day_start.month, day_start.day, 23, 59, 59, 999999) # Inclusive date range filtering brief_responses = brief_responses.filter(BriefResponse.awarded_at.between(day_start, day_end)) brief_responses = brief_responses.options( db.defaultload(BriefResponse.brief).defaultload(Brief.framework).lazyload("*"), db.defaultload(BriefResponse.brief).defaultload(Brief.lot).lazyload("*"), db.defaultload(BriefResponse.brief).defaultload(Brief.awarded_brief_response).lazyload("*"), db.defaultload(BriefResponse.supplier).lazyload("*"), ) if request.args.get('framework'): brief_responses = brief_responses.join(BriefResponse.brief).join(Brief.framework).filter( Brief.framework.has(Framework.slug.in_( framework_slug.strip() for framework_slug in request.args["framework"].split(",") )) ) serialize_kwargs = {"with_data": with_data} if brief_id or supplier_id: return list_result_response(RESOURCE_NAME, brief_responses, serialize_kwargs=serialize_kwargs), 200 return paginated_result_response( result_name=RESOURCE_NAME, results_query=brief_responses, serialize_kwargs=serialize_kwargs, page=page, per_page=current_app.config['DM_API_BRIEF_RESPONSES_PAGE_SIZE'], endpoint='.list_brief_responses', request_args=request.args ), 200
def update_service_status(service_id, status): """ Updates the status parameter of a service, and archives the old one. :param service_id: :param status: :return: the newly updated service in the response """ # Statuses are defined in the Supplier model valid_statuses = [ "published", "enabled", "disabled", "deleted", ] is_valid_service_id_or_400(service_id) service = Service.query.filter( Service.service_id == service_id ).first_or_404() if status not in valid_statuses: valid_statuses_single_quotes = display_list( ["\'{}\'".format(vstatus) for vstatus in valid_statuses] ) abort(400, "'{}' is not a valid status. Valid statuses are {}".format( status, valid_statuses_single_quotes )) update_json = validate_and_return_updater_request() prior_status, service.status = service.status, status commit_and_archive_service(service, update_json, AuditTypes.update_service_status, audit_data={'old_status': prior_status, 'new_status': status}) if prior_status != status: wait_for_response = convert_to_boolean(request.args.get("wait-for-index", "true")) if prior_status == 'published': # If it's being unpublished, delete it from the search api. delete_service_from_index(service, wait_for_response=wait_for_response) else: # If it's being published, index in the search api. index_service(service, wait_for_response=wait_for_response) return single_result_response(RESOURCE_NAME, service), 200
def list_users(): user_query = User.query.order_by(User.id) page = get_valid_page_or_1() # email_address is a primary key email_address = request.args.get('email_address') if email_address: single_user = user_query.filter( User.email_address == email_address.lower() ).first_or_404() return jsonify( users=[single_user.serialize()] ) role = request.args.get('role') if role: if role in User.ROLES: user_query = user_query.filter( User.role == role ) else: abort(400, 'Invalid user role: {}'.format(role)) supplier_id = request.args.get('supplier_id') if supplier_id is not None: try: supplier_id = int(supplier_id) except ValueError: abort(400, "Invalid supplier_id: {}".format(supplier_id)) supplier = Supplier.query.filter(Supplier.supplier_id == supplier_id).all() if not supplier: abort(404, "supplier_id '{}' not found".format(supplier_id)) user_query = user_query.filter(User.supplier_id == supplier_id) personal_data_removed = request.args.get('personal_data_removed') if personal_data_removed is not None: user_query = user_query.filter(User.personal_data_removed == convert_to_boolean(personal_data_removed)) return paginated_result_response( result_name=RESOURCE_NAME, results_query=user_query, page=page, per_page=current_app.config['DM_API_SERVICES_PAGE_SIZE'], endpoint='.list_users', request_args=request.args ), 200
def update_service(service_id): """ Update a service. Looks service up in DB, and updates the JSON listing. """ is_valid_service_id_or_400(service_id) service = Service.query.filter( Service.service_id == service_id).first_or_404() update_details = validate_and_return_updater_request() update = validate_and_return_service_request(service_id) # Check for an update to the copied_to_following_framework flag on the service object if 'copiedToFollowingFramework' in update: if not isinstance(update['copiedToFollowingFramework'], bool): abort(400, "Invalid value for 'copiedToFollowingFramework' supplied") service.copied_to_following_framework = update[ 'copiedToFollowingFramework'] if 'supplierId' in update and int( update['supplierId']) != service.supplier_id: audit_type = AuditTypes.update_service_supplier if len(update.keys()) > 1: abort( 400, "Cannot update supplierID and other fields at the same time") # Discard any other updates update = {'supplierId': int(update['supplierId'])} else: audit_type = (AuditTypes.update_service_admin if request.args.get('user-role') == 'admin' else AuditTypes.update_service) updated_service = update_and_validate_service(service, update) commit_and_archive_service(updated_service, update_details, audit_type) index_service(updated_service, wait_for_response=convert_to_boolean( request.args.get("wait-for-index", "true"))) return jsonify(message="done"), 200
def get_framework_suppliers(framework_slug): framework = Framework.query.filter( Framework.slug == framework_slug).first_or_404() agreement_returned = request.args.get('agreement_returned') supplier_frameworks = SupplierFramework.query.filter( SupplierFramework.framework_id == framework.id) if agreement_returned is not None: if convert_to_boolean(agreement_returned): supplier_frameworks = supplier_frameworks.filter( SupplierFramework.agreement_returned_at.isnot(None)).order_by( SupplierFramework.agreement_returned_at.desc()) else: supplier_frameworks = supplier_frameworks.filter( SupplierFramework.agreement_returned_at.is_(None)) return jsonify(supplierFrameworks=[ supplier_framework.serialize() for supplier_framework in supplier_frameworks ])
def get_framework_suppliers(framework_slug): framework = Framework.query.filter( Framework.slug == framework_slug ).first_or_404() agreement_returned = request.args.get('agreement_returned') supplier_frameworks = SupplierFramework.query.filter( SupplierFramework.framework_id == framework.id ) if agreement_returned is not None: if convert_to_boolean(agreement_returned): supplier_frameworks = supplier_frameworks.filter( SupplierFramework.agreement_returned_at.isnot(None) ).order_by(SupplierFramework.agreement_returned_at.desc()) else: supplier_frameworks = supplier_frameworks.filter( SupplierFramework.agreement_returned_at.is_(None) ) return jsonify(supplierFrameworks=[ supplier_framework.serialize() for supplier_framework in supplier_frameworks ])
def list_audits(): page = get_valid_page_or_1() try: per_page = int(request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') audits = AuditEvent.query.order_by( asc(AuditEvent.created_at) ) audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audits = audits.filter( cast(AuditEvent.created_at, Date) == audit_date ) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: if AuditTypes.is_valid_audit_type(audit_type): audits = audits.filter( AuditEvent.type == audit_type ) else: abort(400, "Invalid audit type") acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == true() ) elif not convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == false() ) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') if not object_id: abort(400, 'object-type cannot be provided without object-id') model = AUDIT_OBJECT_TYPES[object_type] id_field = AUDIT_OBJECT_ID_FIELDS[object_type] audits = audits.join(model, model.id == AuditEvent.object_id) \ .filter(id_field == object_id) elif object_id: abort(400, 'object-id cannot be provided without object-type') audits = audits.paginate( page=page, per_page=per_page ) return jsonify( auditEvents=[audit.serialize() for audit in audits.items], links=pagination_links( audits, '.list_audits', request.args ) )
def list_audits(): page = get_valid_page_or_1() try: per_page = int(request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') earliest_for_each_object = convert_to_boolean(request.args.get('earliest_for_each_object')) if earliest_for_each_object: # the rest of the filters we add will be added against a subquery which we will join back onto the main table # to retrieve the rest of the row. this allows the potentially expensive DISTINCT ON pass to be performed # against an absolutely minimal subset of rows which can probably be pulled straight from an index audits = db.session.query(AuditEvent.id) else: audits = AuditEvent.query audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audit_datetime = datetime.strptime(audit_date, DATE_FORMAT) audits = audits.filter( AuditEvent.created_at.between(audit_datetime, audit_datetime + timedelta(days=1)) ) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: if AuditTypes.is_valid_audit_type(audit_type): audits = audits.filter( AuditEvent.type == audit_type ) else: abort(400, "Invalid audit type") user = request.args.get('user') if user: audits = audits.filter( AuditEvent.user == user ) acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == true() ) elif not convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == false() ) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') ref_model = AUDIT_OBJECT_TYPES[object_type] ext_id_field = AUDIT_OBJECT_ID_FIELDS[object_type] audits = audits.filter(AuditEvent.object.is_type(ref_model)) # "object_id" here is the *external* object_id if object_id: ref_object = ref_model.query.filter( ext_id_field == object_id ).first() if ref_object is None: abort(404, "Object with given object-type and object-id doesn't exist") # this `.identity_key_from_instance(...)[1][0]` is exactly the method used by sqlalchemy_utils' generic # relationship code to extract an object's pk value, so *should* be relatively stable, API-wise. # the `[1]` is to select the pk's *value* rather than the `Column` object and the `[0]` simply fetches # the first of any pk values - generic relationships are already assuming that compound pks aren't in # use by the target. ref_object_pk = class_mapper(ref_model).identity_key_from_instance(ref_object)[1][0] audits = audits.filter( AuditEvent.object_id == ref_object_pk ) elif object_id: abort(400, 'object-id cannot be provided without object-type') if earliest_for_each_object: if not ( acknowledged and convert_to_boolean(acknowledged) is False and audit_type == "update_service" and object_type == "services" ): current_app.logger.warning( "earliest_for_each_object option currently intended for use on acknowledged update_service events. " "If use with any other events is to be regular, the scope of the corresponding partial index " "should be expanded to cover it." ) # we need to join the built-up subquery back onto the AuditEvent table to retrieve the rest of the row audits_subquery = audits.order_by( AuditEvent.object_type, AuditEvent.object_id, AuditEvent.created_at, AuditEvent.id, ).distinct( AuditEvent.object_type, AuditEvent.object_id, ).subquery() audits = AuditEvent.query.join(audits_subquery, audits_subquery.c.id == AuditEvent.id) sort_order = db.desc if convert_to_boolean(request.args.get('latest_first')) else db.asc audits = audits.order_by(sort_order(AuditEvent.created_at), sort_order(AuditEvent.id)) return paginated_result_response( result_name=RESOURCE_NAME, results_query=audits, page=page, per_page=per_page, endpoint='.list_audits', request_args=request.args ), 200
def list_audits(): page = get_valid_page_or_1() try: per_page = int( request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') audits = AuditEvent.query.order_by( desc(AuditEvent.created_at) if convert_to_boolean( request.args.get('latest_first')) else asc(AuditEvent.created_at)) audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audit_datetime = datetime.strptime(audit_date, DATE_FORMAT) audits = audits.filter( AuditEvent.created_at.between( audit_datetime, audit_datetime + timedelta(days=1))) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: if AuditTypes.is_valid_audit_type(audit_type): audits = audits.filter(AuditEvent.type == audit_type) else: abort(400, "Invalid audit type") acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter(AuditEvent.acknowledged == true()) elif not convert_to_boolean(acknowledged): audits = audits.filter(AuditEvent.acknowledged == false()) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') if not object_id: abort(400, 'object-type cannot be provided without object-id') model = AUDIT_OBJECT_TYPES[object_type] id_field = AUDIT_OBJECT_ID_FIELDS[object_type] ref_object = model.query.filter(id_field == object_id).first() if ref_object is None: abort(404, "Object with given object-type and object-id doesn't exist") audits = audits.filter(AuditEvent.object == ref_object) elif object_id: abort(400, 'object-id cannot be provided without object-type') audits = audits.paginate(page=page, per_page=per_page) return jsonify(auditEvents=[audit.serialize() for audit in audits.items], links=pagination_links(audits, '.list_audits', request.args))
def list_audits(): page = get_valid_page_or_1() try: per_page = int(request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') earliest_for_each_object = convert_to_boolean(request.args.get('earliest_for_each_object')) if earliest_for_each_object: # the rest of the filters we add will be added against a subquery which we will join back onto the main table # to retrieve the rest of the row. this allows the potentially expensive DISTINCT ON pass to be performed # against an absolutely minimal subset of rows which can probably be pulled straight from an index audits = db.session.query(AuditEvent.id) else: audits = AuditEvent.query audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audit_datetime = datetime.strptime(audit_date, DATE_FORMAT) audits = audits.filter( AuditEvent.created_at.between(audit_datetime, audit_datetime + timedelta(days=1)) ) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: if AuditTypes.is_valid_audit_type(audit_type): audits = audits.filter( AuditEvent.type == audit_type ) else: abort(400, "Invalid audit type") user = request.args.get('user') if user: audits = audits.filter( AuditEvent.user == user ) data_supplier_id = request.args.get('data-supplier-id') if data_supplier_id: # This filter relies on index `idx_audit_events_data_supplier_id`. See `app..models.main` for its definition. audits = audits.filter( func.coalesce( AuditEvent.__table__.c.data['supplierId'].astext, AuditEvent.__table__.c.data['supplier_id'].astext, ) == data_supplier_id ) acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == true() ) elif not convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == false() ) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') ref_model = AUDIT_OBJECT_TYPES[object_type] ext_id_field = AUDIT_OBJECT_ID_FIELDS[object_type] audits = audits.filter(AuditEvent.object.is_type(ref_model)) # "object_id" here is the *external* object_id if object_id: ref_object = ref_model.query.filter( ext_id_field == object_id ).first() if ref_object is None: abort(404, "Object with given object-type and object-id doesn't exist") # this `.identity_key_from_instance(...)[1][0]` is exactly the method used by sqlalchemy_utils' generic # relationship code to extract an object's pk value, so *should* be relatively stable, API-wise. # the `[1]` is to select the pk's *value* rather than the `Column` object and the `[0]` simply fetches # the first of any pk values - generic relationships are already assuming that compound pks aren't in # use by the target. ref_object_pk = class_mapper(ref_model).identity_key_from_instance(ref_object)[1][0] audits = audits.filter( AuditEvent.object_id == ref_object_pk ) elif object_id: abort(400, 'object-id cannot be provided without object-type') if earliest_for_each_object: if not ( acknowledged and convert_to_boolean(acknowledged) is False and audit_type == "update_service" and object_type == "services" ): current_app.logger.warning( "earliest_for_each_object option currently intended for use on acknowledged update_service events. " "If use with any other events is to be regular, the scope of the corresponding partial index " "should be expanded to cover it." ) # we need to join the built-up subquery back onto the AuditEvent table to retrieve the rest of the row audits_subquery = audits.order_by( AuditEvent.object_type, AuditEvent.object_id, AuditEvent.created_at, AuditEvent.id, ).distinct( AuditEvent.object_type, AuditEvent.object_id, ).subquery() audits = AuditEvent.query.join(audits_subquery, audits_subquery.c.id == AuditEvent.id) sort_order = db.desc if convert_to_boolean(request.args.get('latest_first')) else db.asc audits = audits.order_by(sort_order(AuditEvent.created_at), sort_order(AuditEvent.id)) return paginated_result_response( result_name=RESOURCE_NAME, results_query=audits, page=page, per_page=per_page, endpoint='.list_audits', request_args=request.args ), 200
def list_audits(): page = get_valid_page_or_1() try: per_page = int(request.args.get('per_page', current_app.config['DM_API_SERVICES_PAGE_SIZE'])) except ValueError: abort(400, 'invalid page size supplied') audits = AuditEvent.query.order_by( desc(AuditEvent.created_at) if convert_to_boolean(request.args.get('latest_first')) else asc(AuditEvent.created_at) ) audit_date = request.args.get('audit-date', None) if audit_date: if is_valid_date(audit_date): audit_datetime = datetime.strptime(audit_date, DATE_FORMAT) audits = audits.filter( AuditEvent.created_at.between(audit_datetime, audit_datetime + timedelta(days=1)) ) else: abort(400, 'invalid audit date supplied') audit_type = request.args.get('audit-type') if audit_type: audits = audits.filter( AuditEvent.type == audit_type ) acknowledged = request.args.get('acknowledged', None) if acknowledged and acknowledged != 'all': if is_valid_acknowledged_state(acknowledged): if convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == true() ) elif not convert_to_boolean(acknowledged): audits = audits.filter( AuditEvent.acknowledged == false() ) else: abort(400, 'invalid acknowledged state supplied') object_type = request.args.get('object-type') object_id = request.args.get('object-id') if object_type: if object_type not in AUDIT_OBJECT_TYPES: abort(400, 'invalid object-type supplied') if not object_id: abort(400, 'object-type cannot be provided without object-id') model = AUDIT_OBJECT_TYPES[object_type] id_field = AUDIT_OBJECT_ID_FIELDS[object_type] ref_object = model.query.filter( id_field == object_id ).first() if ref_object is None: abort(404, "Object with given object-type and object-id doesn't exist") audits = audits.filter(AuditEvent.object == ref_object) elif object_id: abort(400, 'object-id cannot be provided without object-type') audits = audits.paginate( page=page, per_page=per_page ) return jsonify( auditEvents=[audit.serialize() for audit in audits.items], links=pagination_links( audits, '.list_audits', request.args ) )