Пример #1
0
 def test_search_advising_notes_narrowed_by_author(self, app, fake_auth):
     """Narrows results for both new and legacy advising notes by author SID."""
     joni = {
         'name': 'Joni Mitchell',
         'uid': '1133399',
         'sid': '800700600',
     }
     not_joni = {
         'name': 'Oliver Heyer',
         'uid': '2040',
     }
     for author in [joni, not_joni]:
         Note.create(
             author_uid=author['uid'],
             author_name=author['name'],
             author_role='Advisor',
             author_dept_codes='COENG',
             sid='11667051',
             subject='Futher on France',
             body='Brigitte has been molded to middle class circumstance',
         )
     fake_auth.login(coe_advisor)
     wide_response = search_advising_notes(search_phrase='Brigitte')
     assert len(wide_response) == 4
     narrow_response = search_advising_notes(search_phrase='Brigitte',
                                             author_csid=joni['sid'])
     assert len(narrow_response) == 2
     new_note, legacy_note = narrow_response[0], narrow_response[1]
     assert new_note['advisorUid'] == joni['uid']
     assert legacy_note['advisorSid'] == joni['sid']
Пример #2
0
 def test_search_advising_notes_paginates_new_and_old(self, app, fake_auth):
     fake_auth.login(coe_advisor)
     for i in range(0, 5):
         Note.create(
             author_uid=coe_advisor,
             author_name='Balloon Man',
             author_role='Spherical',
             author_dept_codes='COENG',
             sid='11667051',
             subject='Planned redundancy',
             body=f'Confounded note {i + 1}',
         )
     response = search_advising_notes(search_phrase='confound',
                                      offset=0,
                                      limit=4)
     assert len(response) == 4
     assert response[0][
         'noteSnippet'] == 'Planned redundancy - <strong>Confounded</strong> note 1'
     assert response[1][
         'noteSnippet'] == 'Planned redundancy - <strong>Confounded</strong> note 2'
     assert response[2][
         'noteSnippet'] == 'Planned redundancy - <strong>Confounded</strong> note 3'
     assert response[3][
         'noteSnippet'] == 'Planned redundancy - <strong>Confounded</strong> note 4'
     response = search_advising_notes(search_phrase='confound',
                                      offset=4,
                                      limit=4)
     assert len(response) == 3
     assert response[0][
         'noteSnippet'] == 'Planned redundancy - <strong>Confounded</strong> note 5'
     assert response[1]['noteSnippet'].startswith(
         'I am <strong>confounded</strong>')
     assert response[2]['noteSnippet'].startswith('...pity the founder')
Пример #3
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))
Пример #4
0
def mock_advising_note(app, db):
    """Create advising note with attachment (mock s3)."""
    with mock_advising_note_s3_bucket(app):
        note_author_uid = '90412'
        base_dir = app.config['BASE_DIR']
        path_to_file = f'{base_dir}/fixtures/mock_advising_note_attachment_1.txt'
        with open(path_to_file, 'r') as file:
            note = Note.create(
                author_uid=note_author_uid,
                author_name='Joni Mitchell',
                author_role='Director',
                author_dept_codes=['UWASC'],
                sid='11667051',
                subject='In France they kiss on main street',
                body="""
                    My darling dime store thief, in the War of Independence
                    Rock 'n Roll rang sweet as victory, under neon signs
                """,
                attachments=[
                    {
                        'name': path_to_file.rsplit('/', 1)[-1],
                        'byte_stream': file.read(),
                    },
                ],
            )
            db.session.add(note)
            std_commit(allow_test_environment=True)
    yield note
    Note.delete(note_id=note.id)
    std_commit(allow_test_environment=True)
Пример #5
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
Пример #6
0
def create_notes():
    benchmark = get_benchmarker('create_notes')
    params = request.form
    sids = _get_sids_for_note_creation()
    benchmark(f'SID count: {len(sids)}')
    body = params.get('body', None)
    is_private = to_bool_or_none(params.get('isPrivate', False))
    subject = params.get('subject', None)
    topics = get_note_topics_from_http_post()
    if not sids or not subject:
        benchmark('end (BadRequest)')
        raise BadRequestError(
            'Note creation requires \'subject\' and \'sids\'')

    dept_codes = dept_codes_where_advising(current_user)
    if current_user.is_admin or not len(dept_codes):
        benchmark('end (Forbidden)')
        raise ForbiddenRequestError(
            'Sorry, only advisors can create advising notes')
    if is_private and not current_user.can_access_private_notes:
        benchmark('end (Forbidden)')
        raise ForbiddenRequestError(
            'Sorry, you are not authorized to manage note privacy.')

    attachments = get_note_attachments_from_http_post(tolerate_none=True)
    benchmark(f'Attachment count: {len(attachments)}')
    body = process_input_from_rich_text_editor(body)
    template_attachment_ids = get_template_attachment_ids_from_http_post()

    if len(sids) == 1:
        note = Note.create(
            **_get_author_profile(),
            attachments=attachments,
            body=body,
            is_private=is_private,
            sid=sids[0],
            subject=subject,
            template_attachment_ids=template_attachment_ids,
            topics=topics,
        )
        response = tolerant_jsonify(
            _boa_note_to_compatible_json(note, note_read=True))
    else:
        response = tolerant_jsonify(
            Note.create_batch(
                **_get_author_profile(),
                attachments=attachments,
                author_id=current_user.to_api_json()['id'],
                body=body,
                is_private=is_private,
                sids=sids,
                subject=subject,
                template_attachment_ids=template_attachment_ids,
                topics=topics,
            ), )
    benchmark('end')
    return response
Пример #7
0
 def test_admin_delete(self, client, fake_auth, mock_coe_advising_note):
     """Admin can delete another user's note."""
     original_count_per_sid = len(Note.get_notes_by_sid(mock_coe_advising_note.sid))
     fake_auth.login(admin_uid)
     note_id = mock_coe_advising_note.id
     response = client.delete(f'/api/notes/delete/{note_id}')
     assert response.status_code == 200
     assert not Note.find_by_id(note_id)
     assert 1 == original_count_per_sid - len(Note.get_notes_by_sid(mock_coe_advising_note.sid))
     assert not Note.update(note_id=note_id, subject='Deleted note cannot be updated')
Пример #8
0
def batch_create_notes():
    params = request.form
    sids = _get_sids_for_note_creation()
    subject = params.get('subject', None)
    body = params.get('body', None)
    topics = get_note_topics_from_http_post()
    if not sids or not subject:
        raise BadRequestError('Note creation requires \'subject\' and \'sid\'')
    user_dept_codes = dept_codes_where_advising(current_user)
    if current_user.is_admin or not len(user_dept_codes):
        raise ForbiddenRequestError(
            'Sorry, only advisors can create advising notes')

    author_profile = _get_author_profile()
    attachments = get_note_attachments_from_http_post(tolerate_none=True)

    note_ids_per_sid = Note.create_batch(
        author_id=current_user.to_api_json()['id'],
        **author_profile,
        subject=subject,
        body=process_input_from_rich_text_editor(body),
        topics=topics,
        sids=sids,
        attachments=attachments,
        template_attachment_ids=get_template_attachment_ids_from_http_post(),
    )
    return tolerant_jsonify(note_ids_per_sid)
Пример #9
0
def create_note():
    params = request.form
    sid = params.get('sid', None)
    subject = params.get('subject', None)
    body = params.get('body', None)
    topics = get_note_topics_from_http_post()
    if not sid or not subject:
        raise BadRequestError('Note creation requires \'subject\' and \'sid\'')
    user_dept_codes = dept_codes_where_advising(current_user)
    if current_user.is_admin or not len(user_dept_codes):
        raise ForbiddenRequestError(
            'Sorry, only advisors can create advising notes.')

    author_profile = _get_author_profile()
    attachments = get_note_attachments_from_http_post(tolerate_none=True)

    note = Note.create(
        **author_profile,
        subject=subject,
        body=process_input_from_rich_text_editor(body),
        topics=topics,
        sid=sid,
        attachments=attachments,
        template_attachment_ids=get_template_attachment_ids_from_http_post(),
    )
    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))
Пример #10
0
def remove_attachment(note_id, attachment_id):
    existing_note = Note.find_by_id(note_id=note_id)
    if not existing_note:
        raise BadRequestError('Note id not found.')
    if existing_note.author_uid != current_user.get_uid() and not current_user.is_admin:
        raise ForbiddenRequestError('You are not authorized to remove attachments from this note.')
    note = Note.delete_attachment(
        note_id=note_id,
        attachment_id=int(attachment_id),
    )
    return tolerant_jsonify(
        _boa_note_to_compatible_json(
            note=note,
            note_read=NoteRead.find_or_create(current_user.get_id(), note_id),
        ),
    )
Пример #11
0
 def test_unauthorized(self, client, fake_auth, mock_coe_advising_note):
     """Advisor cannot delete the note of another."""
     fake_auth.login('6446')
     response = client.delete(
         f'/api/notes/delete/{mock_coe_advising_note.id}')
     assert response.status_code == 403
     assert Note.find_by_id(mock_coe_advising_note.id)
Пример #12
0
    def test_search_new_advising_notes_narrowed_by_date(self, app, fake_auth):
        today = datetime.now().replace(
            hour=0,
            minute=0,
            second=0,
            tzinfo=pytz.timezone(app.config['TIMEZONE'])).astimezone(pytz.utc)
        yesterday = today - timedelta(days=1)
        tomorrow = today + timedelta(days=1)

        fake_auth.login(coe_advisor)
        Note.create(
            author_uid=coe_advisor,
            author_name='Balloon Man',
            author_role='Spherical',
            author_dept_codes='COENG',
            sid='11667051',
            subject='Bryant Park',
            body='There were loads of them',
        )
        assert len(search_advising_notes(search_phrase='Bryant')) == 1

        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_from=yesterday)) == 1
        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_to=yesterday)) == 0
        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_from=yesterday,
                                  datetime_to=yesterday)) == 0

        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_from=tomorrow)) == 0
        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_to=tomorrow)) == 1
        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_from=tomorrow,
                                  datetime_to=tomorrow)) == 0

        assert len(
            search_advising_notes(search_phrase='Bryant',
                                  datetime_from=yesterday,
                                  datetime_to=tomorrow)) == 1
Пример #13
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))
Пример #14
0
 def test_advisor_cannot_delete(self, client, fake_auth,
                                mock_coe_advising_note):
     """Advisor cannot delete her own note."""
     fake_auth.login(mock_coe_advising_note.author_uid)
     response = client.delete(
         f'/api/notes/delete/{mock_coe_advising_note.id}')
     assert response.status_code == 403
     assert Note.find_by_id(mock_coe_advising_note.id)
Пример #15
0
 def test_user_without_advising_data_access(self, client, fake_auth,
                                            mock_coe_advising_note):
     """Denies access to a user who cannot access notes and appointments."""
     fake_auth.login(coe_advisor_no_advising_data_uid)
     response = client.delete(
         f'/api/notes/delete/{mock_coe_advising_note.id}')
     assert response.status_code == 401
     assert Note.find_by_id(mock_coe_advising_note.id)
Пример #16
0
def update_note():
    params = request.form
    note_id = params.get('id', None)
    subject = params.get('subject', None)
    body = params.get('body', None)
    topics = get_note_topics_from_http_post()
    if not note_id or not subject:
        raise BadRequestError('Note requires \'id\' and \'subject\'')
    if Note.find_by_id(note_id=note_id).author_uid != current_user.get_uid():
        raise ForbiddenRequestError('Sorry, you are not the author of this note.')
    note = Note.update(
        note_id=note_id,
        subject=subject,
        body=process_input_from_rich_text_editor(body),
        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))
Пример #17
0
 def test_search_advising_notes_narrowed_by_topic(self, app, fake_auth):
     for topic in ['Good Show', 'Bad Show']:
         Note.create(
             author_uid='1133399',
             author_name='Joni Mitchell',
             author_role='Advisor',
             author_dept_codes='COENG',
             sid='11667051',
             topics=[topic],
             subject='Brigitte',
             body='',
         )
     fake_auth.login(coe_advisor)
     wide_response = search_advising_notes(search_phrase='Brigitte')
     assert len(wide_response) == 4
     narrow_response = search_advising_notes(search_phrase='Brigitte',
                                             topic='Good Show')
     assert len(narrow_response) == 2
Пример #18
0
def add_attachments(note_id):
    note = Note.find_by_id(note_id=note_id)
    if note.author_uid != current_user.get_uid():
        raise ForbiddenRequestError('Sorry, you are not the author of this note.')
    attachments = get_note_attachments_from_http_post()
    attachment_limit = app.config['NOTES_ATTACHMENTS_MAX_PER_NOTE']
    if len(attachments) + len(note.attachments) > attachment_limit:
        raise BadRequestError(f'No more than {attachment_limit} attachments may be uploaded at once.')
    for attachment in attachments:
        note = Note.add_attachment(
            note_id=note_id,
            attachment=attachment,
        )
    return tolerant_jsonify(
        _boa_note_to_compatible_json(
            note=note,
            note_read=NoteRead.find_or_create(current_user.get_id(), note_id),
        ),
    )
Пример #19
0
def get_non_legacy_advising_notes(sid):
    notes_by_id = {}
    for note in [n.to_api_json() for n in Note.get_notes_by_sid(sid)]:
        note_id = note['id']
        notes_by_id[str(note_id)] = note_to_compatible_json(
            note=note,
            attachments=note.get('attachments'),
            topics=note.get('topics'),
        )
    return notes_by_id
Пример #20
0
 def test_search_advising_notes_includes_newly_created(
         self, app, fake_auth):
     fake_auth.login(coe_advisor)
     Note.create(
         author_uid=coe_advisor,
         author_name='Balloon Man',
         author_role='Spherical',
         author_dept_codes='COENG',
         sid='11667051',
         subject='Confound this note',
         body='and its successors and assigns',
     )
     response = search_advising_notes(search_phrase='confound')
     assert len(response) == 3
     assert response[0][
         'noteSnippet'] == '<strong>Confound</strong> this note - and its successors and assigns'
     assert response[1]['noteSnippet'].startswith(
         'I am <strong>confounded</strong>')
     assert response[2]['noteSnippet'].startswith('...pity the founder')
Пример #21
0
def mock_coe_advising_note():
    return Note.create(
        author_uid=coe_advisor_uid,
        author_name='Balloon Man',
        author_role='Spherical',
        author_dept_codes='COENG',
        sid=coe_student['sid'],
        subject='I was walking up Sixth Avenue',
        body='He spattered me with tomatoes, Hummus, chick peas',
    )
Пример #22
0
def add_attachment(note_id):
    if Note.find_by_id(note_id=note_id).author_uid != current_user.get_uid():
        raise ForbiddenRequestError('Sorry, you are not the author of this note.')
    attachments = _get_attachments(request.files)
    if len(attachments) != 1:
        raise BadRequestError('A single attachment file must be supplied.')
    note = Note.add_attachment(
        note_id=note_id,
        attachment=attachments[0],
    )
    note_json = note.to_api_json()
    return tolerant_jsonify(
        note_to_compatible_json(
            note=note_json,
            note_read=NoteRead.find_or_create(current_user.get_id(), note_id),
            attachments=note_json.get('attachments'),
            topics=note_json.get('topics'),
        ),
    )
Пример #23
0
def _create_coe_advisor_note(
    sid,
    subject,
    body='',
    topics=(),
    author_uid=coe_advisor,
    author_name='Balloon Man',
    author_role='Spherical',
    author_dept_codes='COENG',
):
    Note.create(
        author_uid=author_uid,
        author_name=author_name,
        author_role=author_role,
        author_dept_codes=author_dept_codes,
        topics=topics,
        sid=sid,
        subject=subject,
        body=body,
    )
Пример #24
0
 def test_search_advising_notes_narrowed_by_student(self, app, fake_auth):
     """Narrows results for both new and legacy advising notes by student SID."""
     for sid in ['9100000000', '9100000001']:
         Note.create(
             author_uid='1133399',
             author_name='Joni Mitchell',
             author_role='Advisor',
             author_dept_codes='COENG',
             sid=sid,
             subject='Case load',
             body='Another day, another student',
         )
     fake_auth.login(coe_advisor)
     wide_response = search_advising_notes(search_phrase='student')
     assert len(wide_response) == 5
     narrow_response = search_advising_notes(search_phrase='student',
                                             student_csid='9100000000')
     assert len(narrow_response) == 2
     new_note, legacy_note = narrow_response[0], narrow_response[1]
     assert new_note['studentSid'] == '9100000000'
     assert legacy_note['studentSid'] == '9100000000'
Пример #25
0
 def test_unauthorized_update_note(self, app, client, fake_auth, mock_coe_advising_note):
     """Deny user's attempt to edit someone else's note."""
     original_subject = mock_coe_advising_note.subject
     fake_auth.login(asc_advisor_uid)
     assert self._api_note_update(
         app,
         client,
         note_id=mock_coe_advising_note.id,
         subject='Hack someone else\'s subject!',
         body='Hack someone else\'s body!',
         expected_status_code=403,
     )
     assert Note.find_by_id(note_id=mock_coe_advising_note.id).subject == original_subject
Пример #26
0
def get_non_legacy_advising_notes(sid):
    notes_by_id = {}
    for row in Note.get_notes_by_sid(sid):
        note = row.__dict__
        note_id = note['id']
        notes_by_id[str(note_id)] = note_to_compatible_json(
            note=note,
            attachments=[
                a.to_api_json() for a in row.attachments if not a.deleted_at
            ],
            topics=[t.to_api_json() for t in row.topics if not t.deleted_at],
        )
    return notes_by_id
Пример #27
0
def update_note():
    params = request.form
    note_id = params.get('id', None)
    subject = params.get('subject', None)
    body = params.get('body', None)
    topics = _get_topics(params)
    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_]
    if not note_id or not subject:
        raise BadRequestError('Note requires \'id\' and \'subject\'')
    if Note.find_by_id(note_id=note_id).author_uid != current_user.get_uid():
        raise ForbiddenRequestError('Sorry, you are not the author of this note.')
    note = Note.update(
        note_id=note_id,
        subject=subject,
        body=process_input_from_rich_text_editor(body),
        topics=topics,
        attachments=_get_attachments(request.files, tolerate_none=True),
        delete_attachment_ids=delete_attachment_ids,
    )
    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))
Пример #28
0
def mock_asc_advising_note(app, db):
    return Note.create(
        author_uid='1133399',
        author_name='Roberta Joan Anderson',
        author_role='Advisor',
        author_dept_codes=['COENG'],
        sid='3456789012',
        subject='The hissing of summer lawns',
        body="""
            She could see the valley barbecues from her window sill.
            See the blue pools in the squinting sun. Hear the hissing of summer lawns
        """,
        topics=['darkness', 'no color no contrast'],
    )
Пример #29
0
 def test_update_note_with_raw_url_in_body(self, app, client, fake_auth, mock_coe_advising_note):
     """Updates subject and body of note."""
     fake_auth.login(mock_coe_advising_note.author_uid)
     expected_subject = 'There must have been a plague of them'
     body = '<p>They were <a href="http://www.guzzle.com">www.guzzle.com</a> at <b>https://marsh.mallows.com</b> and <a href="http://www.foxnews.com">FOX news</a></p>'  # noqa: E501
     expected_body = '<p>They were <a href="http://www.guzzle.com">www.guzzle.com</a> at <b><a href="https://marsh.mallows.com" target="_blank">https://marsh.mallows.com</a></b> and <a href="http://www.foxnews.com">FOX news</a></p>'  # noqa: E501
     updated_note_response = self._api_note_update(
         app,
         client,
         note_id=mock_coe_advising_note.id,
         subject=expected_subject,
         body=body,
     )
     assert updated_note_response['read'] is True
     updated_note = Note.find_by_id(note_id=mock_coe_advising_note.id)
     assert updated_note.subject == expected_subject
     assert updated_note.body == expected_body
Пример #30
0
 def test_remove_all_topics(self, app, client, fake_auth, new_coe_note,
                            asc_advising_note):
     """Update a note: delete existing topic, leaving none behind."""
     fake_auth.login(asc_advising_note.author_uid)
     expected_topics = []
     updated_note_response = self._api_note_update(
         app,
         client,
         note_id=asc_advising_note.id,
         subject=asc_advising_note.subject,
         body=asc_advising_note.body,
         topics=expected_topics,
     )
     assert updated_note_response['read'] is True
     assert len(updated_note_response['topics']) == 0
     updated_note = Note.find_by_id(note_id=asc_advising_note.id)
     assert len(updated_note.topics) == 0