示例#1
0
def _curated_group_with_complete_student_profiles(curated_group_id,
                                                  order_by='last_name',
                                                  term_id=None,
                                                  offset=0,
                                                  limit=50):
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} with student profiles')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if not _can_current_user_view_curated_group(curated_group):
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, cannot view curated group {curated_group.id}'
        )
    api_json = curated_group.to_api_json(order_by=order_by,
                                         offset=offset,
                                         limit=limit)
    sids = [s['sid'] for s in api_json['students']]
    benchmark('begin profile query')
    api_json['students'] = get_summary_student_profiles(
        sids, term_id=term_id, include_historical=True)
    benchmark('begin alerts query')
    Alert.include_alert_counts_for_students(
        viewer_user_id=current_user.get_id(), group=api_json)
    benchmark('end')
    benchmark('begin get_referencing_cohort_ids')
    api_json[
        'referencingCohortIds'] = curated_group.get_referencing_cohort_ids()
    benchmark('end')
    return api_json
示例#2
0
def get_cohort(cohort_id):
    benchmark = get_benchmarker(f'cohort {cohort_id} get_cohort')
    benchmark('begin')
    filter_keys = list(request.args.keys())
    order_by = get_param(request.args, 'orderBy', None)
    if is_unauthorized_search(filter_keys, order_by):
        raise ForbiddenRequestError(
            'You are unauthorized to access student data managed by other departments'
        )
    include_students = to_bool(get_param(request.args, 'includeStudents'))
    include_students = True if include_students is None else include_students
    offset = get_param(request.args, 'offset', 0)
    limit = get_param(request.args, 'limit', 50)
    benchmark('begin cohort filter query')
    cohort = CohortFilter.find_by_id(
        int(cohort_id),
        order_by=order_by,
        offset=int(offset),
        limit=int(limit),
        include_alerts_for_user_id=current_user.get_id(),
        include_profiles=True,
        include_students=include_students,
    )
    if cohort and _can_current_user_view_cohort(cohort):
        _decorate_cohort(cohort)
        benchmark('end')
        return tolerant_jsonify(cohort)
    else:
        raise ResourceNotFoundError(
            f'No cohort found with identifier: {cohort_id}')
示例#3
0
def appointment_check_in(appointment_id):
    appointment = Appointment.find_by_id(appointment_id)
    if not appointment:
        raise ResourceNotFoundError('Unknown path')
    if appointment.dept_code in _dept_codes_with_scheduler_privilege():
        params = request.get_json()
        advisor_uid = params.get('advisorUid', None)
        if not advisor_uid:
            raise BadRequestError(
                'Appointment check-in requires \'advisor_uid\'')
        appointment = Appointment.check_in(
            appointment_id=appointment_id,
            checked_in_by=current_user.get_id(),
            advisor_dept_codes=params.get('advisorDeptCodes', None),
            advisor_name=params.get('advisorName', None),
            advisor_role=params.get('advisorRole', None),
            advisor_uid=advisor_uid,
        )
        api_json = appointment.to_api_json(current_user.get_id())
        _put_student_profile_per_appointment([api_json])
        return tolerant_jsonify(api_json)
    else:
        raise ForbiddenRequestError(
            f'You are unauthorized to manage {appointment.dept_code} appointments.'
        )
def update_note_template():
    params = request.form
    note_template_id = params.get('id', None)
    subject = params.get('subject', None)
    if not subject:
        raise BadRequestError('Requires \'subject\'')
    body = params.get('body', None)
    topics = get_note_topics_from_http_post()
    delete_ids_ = params.get('deleteAttachmentIds') or []
    delete_ids_ = delete_ids_ if isinstance(
        delete_ids_, list) else str(delete_ids_).split(',')
    delete_attachment_ids = [int(id_) for id_ in delete_ids_]
    note_template = NoteTemplate.find_by_id(note_template_id=note_template_id)
    if not note_template:
        raise ResourceNotFoundError('Template not found')
    if note_template.creator_id != current_user.get_id():
        raise ForbiddenRequestError('Template not available.')
    note_template = NoteTemplate.update(
        attachments=get_note_attachments_from_http_post(tolerate_none=True),
        body=process_input_from_rich_text_editor(body),
        delete_attachment_ids=delete_attachment_ids,
        is_private=to_bool_or_none(params.get('isPrivate', False)),
        note_template_id=note_template_id,
        subject=subject,
        topics=topics,
    )
    return tolerant_jsonify(note_template.to_api_json())
示例#5
0
def get_cohort(cohort_id):
    if is_unauthorized_search(request.args):
        raise ForbiddenRequestError(
            'You are unauthorized to access student data managed by other departments'
        )
    include_students = util.to_bool_or_none(
        util.get(request.args, 'includeStudents'))
    include_students = True if include_students is None else include_students
    order_by = util.get(request.args, 'orderBy', None)
    offset = util.get(request.args, 'offset', 0)
    limit = util.get(request.args, 'limit', 50)
    cohort = CohortFilter.find_by_id(int(cohort_id))
    if cohort and can_view_cohort(current_user, cohort):
        cohort = decorate_cohort(
            cohort,
            order_by=order_by,
            offset=int(offset),
            limit=int(limit),
            include_alerts_for_uid=current_user.uid,
            include_profiles=True,
            include_students=include_students,
        )
        return tolerant_jsonify(cohort)
    else:
        raise ResourceNotFoundError(
            f'No cohort found with identifier: {cohort_id}')
示例#6
0
def _dev_auth_login(uid, password):
    if app.config['DEVELOPER_AUTH_ENABLED']:
        logger = app.logger
        if password != app.config['DEVELOPER_AUTH_PASSWORD']:
            logger.error('Dev-auth: Wrong password')
            return tolerant_jsonify({'message': 'Invalid credentials'}, 401)
        user_id = AuthorizedUser.get_id_per_uid(uid)
        user = user_id and app.login_manager.user_callback(user_id=user_id,
                                                           flush_cached=True)
        if user is None:
            logger.error(
                f'Dev-auth: User with UID {uid} is not registered in BOA.')
            return tolerant_jsonify(
                {
                    'message':
                    f'Sorry, user with UID {uid} is not registered to use BOA.'
                }, 403)
        if not user.is_active:
            logger.error(
                f'Dev-auth: UID {uid} is registered with BOA but not active.')
            return tolerant_jsonify(
                {
                    'message':
                    f'Sorry, user with UID {uid} is not authorized to use BOA.'
                }, 403)
        logger.info(f'Dev-auth used to log in as UID {uid}')
        login_user(user, force=True, remember=True)
        return tolerant_jsonify(current_user.to_api_json())
    else:
        raise ResourceNotFoundError('Unknown path')
示例#7
0
def get_appointment(appointment_id):
    appointment = Appointment.find_by_id(appointment_id)
    if not appointment:
        raise ResourceNotFoundError('Unknown path')
    api_json = appointment.to_api_json(current_user.get_id())
    _put_student_profile_per_appointment([api_json])
    return tolerant_jsonify(api_json)
示例#8
0
def get_degree_checks(uid):
    sid = get_sid_by_uid(uid)
    if sid:
        return tolerant_jsonify(
            DegreeProgressTemplate.find_by_sid(student_sid=sid))
    else:
        raise ResourceNotFoundError('Student not found')
示例#9
0
def students_with_alerts(cohort_id):
    benchmark = get_benchmarker(f'cohort {cohort_id} students_with_alerts')
    benchmark('begin')
    offset = get_param(request.args, 'offset', 0)
    limit = get_param(request.args, 'limit', 50)
    cohort = CohortFilter.find_by_id(
        cohort_id,
        include_alerts_for_user_id=current_user.get_id(),
        include_students=False,
        alert_offset=offset,
        alert_limit=limit,
    )
    benchmark('fetched cohort')
    if cohort and _can_current_user_view_cohort(cohort):
        _decorate_cohort(cohort)
        students = cohort.get('alerts', [])
        alert_sids = [s['sid'] for s in students]
        alert_profiles = get_summary_student_profiles(alert_sids)
        benchmark('fetched student profiles')
        alert_profiles_by_sid = {p['sid']: p for p in alert_profiles}
        for student in students:
            student.update(alert_profiles_by_sid[student['sid']])
            # The enrolled units count is the one piece of term data we want to preserve.
            if student.get('term'):
                student['term'] = {
                    'enrolledUnits': student['term'].get('enrolledUnits')
                }
    else:
        raise ResourceNotFoundError(
            f'No cohort found with identifier: {cohort_id}')
    benchmark('end')
    return tolerant_jsonify(students)
示例#10
0
def update_note():
    params = request.form
    body = params.get('body', None)
    is_private = to_bool_or_none(params.get('isPrivate', False))
    note_id = params.get('id', None)
    subject = params.get('subject', None)
    topics = get_note_topics_from_http_post()

    note = Note.find_by_id(note_id=note_id) if note_id else None
    if not note:
        raise ResourceNotFoundError('Note not found')
    if not subject:
        raise BadRequestError('Note subject is required')
    if note.author_uid != current_user.get_uid():
        raise ForbiddenRequestError(
            'Sorry, you are not the author of this note.')
    if (is_private is not note.is_private
        ) and not current_user.can_access_private_notes:
        raise ForbiddenRequestError(
            'Sorry, you are not authorized to manage note privacy')

    note = Note.update(
        body=process_input_from_rich_text_editor(body),
        is_private=is_private,
        note_id=note_id,
        subject=subject,
        topics=topics,
    )
    note_read = NoteRead.find_or_create(current_user.get_id(), note_id)
    return tolerant_jsonify(
        _boa_note_to_compatible_json(note=note, note_read=note_read))
示例#11
0
def update_course(course_id):
    course = DegreeProgressCourse.find_by_id(course_id)
    if course:
        params = request.get_json()
        accent_color = normalize_accent_color(get_param(params, 'accentColor'))

        grade = get_param(params, 'grade')
        grade = course.grade if grade is None else grade
        name = get_param(params, 'name')
        name = course.display_name if name is None else name
        if name is None:
            raise BadRequestError('name is required.')

        note = get_param(params, 'note')
        # Courses are mapped to degree_progress_unit_requirements
        value = get_param(request.get_json(), 'unitRequirementIds')
        unit_requirement_ids = list(filter(
            None, value.split(','))) if isinstance(value, str) else value
        units = get_param(params, 'units') or None

        course = DegreeProgressCourse.update(
            accent_color=accent_color,
            course_id=course_id,
            grade=grade,
            name=name,
            note=note,
            unit_requirement_ids=unit_requirement_ids,
            units=units,
        )
        # Update updated_at date of top-level record
        DegreeProgressTemplate.refresh_updated_at(course.degree_check_id,
                                                  current_user.get_id())
        return tolerant_jsonify(course.to_api_json())
    else:
        raise ResourceNotFoundError('Course not found.')
def get_students_with_alerts(curated_group_id):
    offset = get_param(request.args, 'offset', 0)
    limit = get_param(request.args, 'limit', 50)
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} students_with_alerts')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if not _can_current_user_view_curated_group(curated_group):
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, cannot view curated group {curated_group.id}'
        )
    students = Alert.include_alert_counts_for_students(
        benchmark=benchmark,
        viewer_user_id=current_user.get_id(),
        group={'sids': CuratedGroup.get_all_sids(curated_group_id)},
        count_only=True,
        offset=offset,
        limit=limit,
    )
    alert_count_per_sid = {}
    for s in list(filter(lambda s: s.get('alertCount') > 0, students)):
        sid = s.get('sid')
        alert_count_per_sid[sid] = s.get('alertCount')
    sids = list(alert_count_per_sid.keys())
    benchmark('begin profile query')
    students_with_alerts = get_student_profile_summaries(sids=sids)
    benchmark('end profile query')
    for student in students_with_alerts:
        student['alertCount'] = alert_count_per_sid[student['sid']]
    benchmark('end')
    return tolerant_jsonify(students_with_alerts)
示例#13
0
def update_appointment(appointment_id):
    appointment = Appointment.find_by_id(appointment_id)
    if not appointment:
        raise ResourceNotFoundError('Unknown path')
    has_privilege = current_user.is_admin or appointment.dept_code in _dept_codes_with_scheduler_privilege()
    if not has_privilege:
        raise ForbiddenRequestError(f'You are unauthorized to manage {appointment.dept_code} appointments.')
    params = request.get_json()
    details = params.get('details', None)
    scheduled_time = params.get('scheduledTime', None)
    if scheduled_time:
        scheduled_time = localized_timestamp_to_utc(scheduled_time)
    student_contact_info = params.get('studentContactInfo', None)
    student_contact_type = params.get('studentContactType', None)
    topics = params.get('topics', None)
    appointment.update(
        details=process_input_from_rich_text_editor(details),
        scheduled_time=scheduled_time,
        student_contact_info=student_contact_info,
        student_contact_type=student_contact_type,
        topics=topics,
        updated_by=current_user.get_id(),
    )
    api_json = appointment.to_api_json(current_user.get_id())
    _put_student_profile_per_appointment([api_json])
    return tolerant_jsonify(api_json)
示例#14
0
def get_waitlist(dept_code):
    def _is_current_user_authorized():
        return current_user.is_admin or dept_code in _dept_codes_with_scheduler_privilege()

    dept_code = dept_code.upper()
    if dept_code not in BERKELEY_DEPT_CODE_TO_NAME:
        raise ResourceNotFoundError(f'Unrecognized department code: {dept_code}')
    elif _is_current_user_authorized():
        show_all_statuses = current_user.is_drop_in_advisor or current_user.is_admin
        statuses = appointment_event_type.enums if show_all_statuses else ['reserved', 'waiting']
        unresolved = []
        resolved = []
        for appointment in Appointment.get_drop_in_waitlist(dept_code, statuses):
            a = appointment.to_api_json(current_user.get_id())
            if a['status'] in ['reserved', 'waiting']:
                unresolved.append(a)
            else:
                resolved.append(a)
        _put_student_profile_per_appointment(unresolved)
        _put_student_profile_per_appointment(resolved)
        return tolerant_jsonify({
            'advisors': drop_in_advisors_for_dept_code(dept_code),
            'waitlist': {
                'unresolved': unresolved,
                'resolved': resolved,
            },
        })
    else:
        raise ForbiddenRequestError(f'You are unauthorized to manage {dept_code} appointments.')
def get_note_template(note_template_id):
    note_template = NoteTemplate.find_by_id(note_template_id=note_template_id)
    if not note_template:
        raise ResourceNotFoundError('Template not found')
    if note_template.creator_id != current_user.get_id():
        raise ForbiddenRequestError('Template not available')
    return tolerant_jsonify(note_template.to_api_json())
示例#16
0
def _curated_group_with_complete_student_profiles(curated_group_id,
                                                  order_by='last_name',
                                                  offset=0,
                                                  limit=50):
    benchmark = get_benchmarker(
        f'curated group {curated_group_id} with student profiles')
    benchmark('begin')
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'Sorry, no curated group found with id {curated_group_id}.')
    if curated_group.owner_id != current_user.get_id():
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, does not own curated group {curated_group.id}'
        )
    api_json = curated_group.to_api_json(order_by=order_by,
                                         offset=offset,
                                         limit=limit)
    sids = [s['sid'] for s in api_json['students']]
    benchmark('begin profile query')
    api_json['students'] = get_summary_student_profiles(sids)
    benchmark('begin alerts query')
    Alert.include_alert_counts_for_students(
        viewer_user_id=current_user.get_id(), group=api_json)
    benchmark('end')
    return api_json
示例#17
0
def create_appointment():
    params = request.get_json()
    dept_code = params.get('deptCode', None)
    sid = params.get('sid', None)
    advisor_uid = params.get('advisorUid', None)
    appointment_type = params.get('appointmentType', None)
    topics = params.get('topics', None)
    if not dept_code or not sid or not appointment_type or not len(topics):
        raise BadRequestError(
            'Appointment creation: required parameters were not provided')
    dept_code = dept_code.upper()
    if dept_code not in BERKELEY_DEPT_CODE_TO_NAME:
        raise ResourceNotFoundError(
            f'Unrecognized department code: {dept_code}')
    if dept_code not in _dept_codes_with_scheduler_privilege():
        raise ForbiddenRequestError(
            f'You are unauthorized to manage {dept_code} appointments.')
    appointment = Appointment.create(
        advisor_uid=advisor_uid,
        appointment_type=appointment_type,
        created_by=current_user.get_id(),
        dept_code=dept_code,
        details=params.get('details', None),
        student_sid=sid,
        topics=topics,
    )
    AppointmentRead.find_or_create(current_user.get_id(), appointment.id)
    api_json = appointment.to_api_json(current_user.get_id())
    _put_student_profile_per_appointment([api_json])
    return tolerant_jsonify(api_json)
示例#18
0
def get_note(note_id):
    note = Note.find_by_id(note_id=note_id)
    if not note:
        raise ResourceNotFoundError('Note not found')
    note_read = NoteRead.when_user_read_note(current_user.get_id(),
                                             str(note.id))
    return tolerant_jsonify(
        _boa_note_to_compatible_json(note=note, note_read=note_read))
示例#19
0
def delete_note(note_id):
    if not current_user.is_admin:
        raise ForbiddenRequestError('Sorry, you are not authorized to delete notes.')
    note = Note.find_by_id(note_id=note_id)
    if not note:
        raise ResourceNotFoundError('Note not found')
    Note.delete(note_id=note_id)
    return tolerant_jsonify({'message': f'Note {note_id} deleted'}), 200
示例#20
0
文件: util.py 项目: cesarvh/boac
def is_unauthorized_domain(domain):
    if domain not in ['default', 'admitted_students']:
        raise BadRequestError(f'Invalid domain: {domain}')
    elif domain == 'admitted_students' and not app.config[
            'FEATURE_FLAG_ADMITTED_STUDENTS']:
        raise ResourceNotFoundError('Unknown path')
    return domain == 'admitted_students' and not current_user.is_admin and 'ZCEEE' not in dept_codes_where_advising(
        current_user)
示例#21
0
def user_analytics(uid):
    feed = get_student_and_terms(uid)
    if not feed:
        raise ResourceNotFoundError('Unknown student')
    # CalCentral's Student Overview page is advisors' official information source for the student.
    feed[
        'studentProfileLink'] = f'https://calcentral.berkeley.edu/user/overview/{uid}'
    return tolerant_jsonify(feed)
示例#22
0
def become():
    if app.config['DEVELOPER_AUTH_ENABLED']:
        params = request.get_json() or {}
        logout_user()
        _dev_login(params.get('uid'), app.config['DEVELOPER_AUTH_PASSWORD'])
        return redirect('/')
    else:
        raise ResourceNotFoundError('Unknown path')
示例#23
0
def get_student_by_uid(uid):
    profile_only = to_bool_or_none(request.args.get('profileOnly'))
    student = get_student_and_terms_by_uid(uid)
    if not student:
        raise ResourceNotFoundError('Unknown student')
    if not profile_only:
        put_notifications(student)
        _put_degree_checks_json(student)
    return tolerant_jsonify(student)
示例#24
0
def assign_course(course_id):
    params = request.get_json()
    course = DegreeProgressCourse.find_by_id(course_id)
    if course:
        # Get existing category assignment. If it's a placeholder then delete it at end of transaction.
        previous_category = DegreeProgressCategory.find_by_id(
            course.category_id) if course.category_id else None
        category_id = get_param(params, 'categoryId')
        category = DegreeProgressCategory.find_by_id(
            category_id) if category_id else None
        if category:
            if category.template_id != course.degree_check_id:
                raise BadRequestError(
                    'The category and course do not belong to the same degree_check instance.'
                )
            children = DegreeProgressCategory.find_by_parent_category_id(
                parent_category_id=category_id)
            if next((c for c in children if c.category_type == 'Subcategory'),
                    None):
                raise BadRequestError(
                    'A course cannot be assigned to a category with a subcategory.'
                )
            course = DegreeProgressCourse.assign(category_id=category_id,
                                                 course_id=course.id)

        else:
            # When user un-assigns a course we delete all copies of that course,.
            for copy_of_course in DegreeProgressCourse.get_courses(
                    degree_check_id=course.degree_check_id,
                    manually_created_at=course.manually_created_at,
                    manually_created_by=course.manually_created_by,
                    section_id=course.section_id,
                    sid=course.sid,
                    term_id=course.term_id,
            ):
                if copy_of_course.id != course.id:
                    DegreeProgressCourseUnitRequirement.delete(
                        copy_of_course.id)
                    category = DegreeProgressCategory.find_by_id(
                        copy_of_course.category_id)
                    if category and 'Placeholder' in category.category_type:
                        DegreeProgressCategory.delete(category.id)
                    DegreeProgressCourse.delete(copy_of_course)
            ignore = to_bool_or_none(get_param(params, 'ignore'))
            course = DegreeProgressCourse.unassign(course_id=course.id,
                                                   ignore=ignore)

        # If previous assignment was a "placeholder" category then delete it.
        if previous_category and 'Placeholder' in previous_category.category_type:
            DegreeProgressCategory.delete(previous_category.id)

        # Update updated_at date of top-level record
        DegreeProgressTemplate.refresh_updated_at(course.degree_check_id,
                                                  current_user.get_id())
        return tolerant_jsonify(course.to_api_json())
    else:
        raise ResourceNotFoundError('Course not found.')
def delete_note_template(note_template_id):
    note_template = NoteTemplate.find_by_id(note_template_id=note_template_id)
    if not note_template:
        raise ResourceNotFoundError('Template not found')
    if note_template.creator_id != current_user.get_id():
        raise ForbiddenRequestError('Template not available')
    NoteTemplate.delete(note_template_id=note_template_id)
    return tolerant_jsonify(
        {'message': f'Note template {note_template_id} deleted'}), 200
def clone_degree_template(template_id, name=None, sid=None):
    template = DegreeProgressTemplate.find_by_id(template_id)
    if template_id and not template:
        raise ResourceNotFoundError(
            f'No template found with id={template_id}.')
    if name:
        validate_template_upsert(name=name, template_id=template_id)

    created_by = current_user.get_id()
    return clone(template, created_by, name=name, sid=sid)
def delete_unit_requirement(unit_requirement_id):
    unit_requirement = DegreeProgressUnitRequirement.find_by_id(unit_requirement_id)
    if unit_requirement:
        DegreeProgressUnitRequirement.delete(unit_requirement_id)
        # Update updated_at date of top-level record
        DegreeProgressTemplate.refresh_updated_at(unit_requirement.template_id, current_user.get_id())

        return tolerant_jsonify({'message': f'Unit requirement {unit_requirement_id} deleted'}), 200
    else:
        raise ResourceNotFoundError(f'No unit_requirement found with id={unit_requirement_id}.')
示例#28
0
def unreserve_appointment(appointment_id):
    appointment = Appointment.find_by_id(appointment_id)
    if not appointment:
        raise ResourceNotFoundError('Unknown path')
    has_privilege = current_user.is_admin or appointment.dept_code in _dept_codes_with_scheduler_privilege()
    if not has_privilege:
        raise ForbiddenRequestError(f'You are unauthorized to manage appointment {appointment_id}.')
    if appointment.status != 'reserved':
        raise BadRequestError(appointment.to_api_json(current_user.get_id()))
    _set_appointment_to_waiting(appointment)
    return Response(status=200)
def remove_student_from_curated_group(curated_group_id, sid):
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'No curated group found with id: {curated_group_id}')
    if curated_group.owner_id != current_user.get_id():
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, does not own curated group {curated_group.id}'
        )
    CuratedGroup.remove_student(curated_group_id, sid)
    return tolerant_jsonify(curated_group.to_api_json(include_students=False))
def delete_curated_group(curated_group_id):
    curated_group = CuratedGroup.find_by_id(curated_group_id)
    if not curated_group:
        raise ResourceNotFoundError(
            f'No curated group found with id: {curated_group_id}')
    if curated_group.owner_id != current_user.get_id():
        raise ForbiddenRequestError(
            f'Current user, {current_user.get_uid()}, does not own curated group {curated_group.id}'
        )
    CuratedGroup.delete(curated_group_id)
    return tolerant_jsonify(
        {'message': f'Curated group {curated_group_id} has been deleted'}), 200