def test_approved_coordinator_logged_in(self): ''' This tests that informal chat contracts are visible to an approved coordinator logged in, whose community has been approved to participate ''' scenario = scenarios.InternshipWeekScenario(week=11) self.client.force_login(scenario.coordinator.account) self.assert_permission_approved_on_informal_chat_contacts()
def test_project_submission_to_approved_community(self): """ This tests submitting a project to a community that is approved to participate in this round. - Create a new RoundPage for the upcoming round, with an approved community - Go to the community read-only page - Log in as a mentor from the previous round for this community - Click the 'Submit Project' button, fill out project description - Be redirected to the MentorApproval form, fill that out - Be redirected to the ProjectSkills form, fill that out - Be redirected to the ProjectCommunicationChannels form, fill that out - Finally be done! \o/ - Coordinator receives email about the pending project - The community read-only page should not reflect the project was submitted, except to the mentor who submitted it """ scenario = scenarios.InternshipWeekScenario(week=10, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='pingnew') participation = factories.ParticipationFactory( community=scenario.community, participating_round=current_round, approval_status=ApprovalStatus.APPROVED) self.check_project_submission(scenario, current_round, participation)
def submit_failed_community_signup(self, current_round): scenario = scenarios.InternshipWeekScenario(week = 10, community__name='Debian', community__slug='debian') self.client.force_login(scenario.coordinator.account) response = self.coordinator_signs_up_community_to_participate( reverse('participation-action', kwargs={'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }), ) with self.assertRaises(Participation.DoesNotExist): p = Participation.objects.get(community__slug=scenario.participation.community.slug, participating_round__slug=current_round.slug) self.assertNotEqual(response.status_code, 302)
def test_community_read_only_submission_text_cfp_closed(self): """ This tests how the page for coordinators and mentors of a community looks between rounds (after interns have been selected but before the next round has been announced). Test home/templates/home/community_read_only.html: - Create a community that has been approved to participate in a past round - No new RoundPage for the upcoming round - Check: - Warning card about CFP not being open is visible - The 'Submit a Project Proposal' button is not visible - The 'Submit an Outreachy Intern Project Proposal' heading is not visible - Ensure those checks are true for all visitor types """ scenario = scenarios.InternshipWeekScenario(week=1, community__name='Debian', community__slug='debian') community_read_only_path = reverse( 'community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, }) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': scenario.participation.participating_round.slug, 'community_slug': scenario.participation.community.slug, }) visitors = self.get_visitors_from_past_round(scenario) for visitor_type, visitor in visitors: with self.subTest(visitor_type=visitor_type): self.client.logout() if visitor: self.client.force_login(visitor) response = self.client.get(community_read_only_path) self.assertEqual(response.status_code, 200) self.assertContains( response, '<div class="card-header text-white bg-warning">Project and community CFP is currently closed</div>', html=True) self.assertNotContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertNotContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True)
def test_intern_in_good_standing_and_logged_in(self): """ This tests that informal chat contracts are visible to an intern who is logged in, but not authorized to see them. """ scenario = scenarios.InternshipWeekScenario(week=11) # Applicant 1 is an approved intern # and should be able to see the informal chat contacts self.client.force_login(scenario.applicant1.applicant.account) self.assert_permission_approved_on_informal_chat_contacts()
def test_not_authorized_and_logged_in(self): """ This tests that informal chat contracts are not visible to someone who is logged in, but not authorized to see them. """ scenario = scenarios.InternshipWeekScenario(week=11) # Applicant 4 has a pending initial application # and should not be able to see the informal chat contacts self.client.force_login(scenario.applicant4.applicant.account) self.assert_permission_denied_on_informal_chat_contacts()
def test_mentor_with_intern_and_logged_in(self): """ This tests that informal chat contracts are visible to a mentor who is logged in, who does have an organizer approved intern. """ scenario = scenarios.InternshipWeekScenario(week=11) # Mentor 1 is mentoring Applicant 1 (who is an approved intern) # and should be able to see the informal chat contacts mentors = models.MentorRelationship.objects.filter( intern_selection=scenario.intern_selection1) self.client.force_login(mentors[0].mentor.mentor.account) self.assert_permission_approved_on_informal_chat_contacts()
def test_approved_coordinator_unapproved_community_logged_in(self): ''' This tests that informal chat contracts are NOT visible to an approved coordinator logged in, whose community has NOT been approved to participate ''' scenario = scenarios.InternshipWeekScenario(week=11) for community_approval_status in [ models.ApprovalStatus.PENDING, models.ApprovalStatus.WITHDRAWN, models.ApprovalStatus.REJECTED ]: with self.subTest( community_approval_status=community_approval_status): scenario.participation.approval_status = community_approval_status scenario.participation.save() self.client.force_login(scenario.coordinator.account) self.assert_permission_denied_on_informal_chat_contacts() self.client.logout()
def test_mentor_with_no_intern_and_logged_in(self): """ This tests that informal chat contracts are not visible to a mentor who is logged in, but does not have an organizer approved intern. """ scenario = scenarios.InternshipWeekScenario(week=11) scenario.intern_selection = factories.InternSelectionFactory( round=scenario.round, funding_source=models.InternSelection.ORG_FUNDED, organizer_approved=False, ) # The unapproved intern's mentor # should not be able to see the informal chat contacts mentors = models.MentorRelationship.objects.filter( intern_selection=scenario.intern_selection) self.client.force_login(mentors[0].mentor.mentor.account) self.assert_permission_denied_on_informal_chat_contacts()
def test_community_read_only_submission_text_cfp_open_uncertain_participation( self): """ This tests how the page for coordinators and mentors of a community looks after a new round has been announced, but before the community signs up to participate. Test home/templates/home/community_read_only.html: - Create a community that has been approved to participate in a past round - Create a new RoundPage for the upcoming round - Check: - The 'Not Participating' status is visible - The 'Coordinate for This Community' button is visible to anyone who is not a coordinator - The 'Community will participate' button is visible to a coordinator - The 'Community will not participate' button is visible to a coordinator - The 'Submit a Project Proposal' button is not visible - The 'Submit an Outreachy Intern Project Proposal' heading is not visible """ scenario = scenarios.InternshipWeekScenario(week=10, community__name='Debian', community__slug='debian') community_read_only_path = reverse( 'community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, }) current_round = factories.RoundPageFactory(start_from='pingnew') project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) coordinator_signup_path = reverse( 'coordinatorapproval-action', kwargs={ 'action': 'submit', 'community_slug': scenario.participation.community.slug, }) community_does_participate_path = reverse( 'participation-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) community_does_not_participate_path = reverse( 'participation-action', kwargs={ 'action': 'withdraw', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) visitors = self.get_visitors_from_past_round(scenario) for visitor_type, visitor in visitors: with self.subTest(visitor_type=visitor_type): self.client.logout() if visitor: self.client.force_login(visitor) response = self.client.get(community_read_only_path) self.assertEqual(response.status_code, 200) self.assertContains( response, '<span class="badge badge-pill badge-warning">Not Participating</span>', html=True) if visitor_type != 'coordinator': self.assertContains( response, '<a href="{}" class="btn btn-success">Coordinate for This Community</a>' .format(coordinator_signup_path), html=True) else: self.assertContains( response, '<a href="{}" class="btn btn-success">Community will participate</a>' .format(community_does_participate_path), html=True) self.assertContains( response, '<a href="{}" class="btn btn-warning">Community will not participate</a>' .format(community_does_not_participate_path), html=True) self.assertNotContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertNotContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True)
def test_project_hard_deadline(self): """ This tests submitting a project after the deadline for project approval fails: - Create a new RoundPage for the upcoming round with the project submission deadline passed - Create an approved community - Go to the community read-only page - There should not be a 'Submit Project' button - Submitting a project directly via the URL should fail """ scenario = scenarios.InternshipWeekScenario(week=15, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='lateprojects') participation = factories.ParticipationFactory( community=scenario.community, participating_round=current_round, approval_status=ApprovalStatus.APPROVED) # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get(reverse('community-cfp')) self.assertEqual(response.status_code, 200) self.assertContains( response, '<div class="card-header text-white bg-warning">Project and community CFP is currently closed</div>', html=True) # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get( reverse('community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, })) self.assertContains( response, '<div class="card-header text-white bg-warning">Project and community CFP is currently closed</div>', html=True) # Check that there is not a submit project button self.assertNotContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertContains(response, '<h2>Project Submission Deadline Passed</h2>', html=True) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) self.assertNotContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True) sponsorship = factories.SponsorshipFactory( participation=participation, name='Software in the Public Interest - Debian', amount=13000) visitors = self.get_visitors_from_past_round(scenario) # Submit project description self.client.force_login(scenario.mentor.account) response = self.client.post( project_submission_path, { 'approved_license': 'on', 'no_proprietary_software': 'on', 'longevity': '2Y', 'community_size': '20', 'short_title': 'Improve Debian bioinformatics packages test coverage', 'long_description': 'The Debian Med project has packaged a lot of <a href="http://blends.debian.org/med/tasks/bio">applications for bioinformatics</a>. You will be improving the test coverage of those packages.', 'minimum_system_requirements': 'A system running Debian Linux', 'contribution_tasks': 'Look at issues marked newcomers-welcome.', 'repository': 'https://salsa.debian.org/med-team', 'issue_tracker': 'https://bugs.debian.org/', 'newcomer_issue_tag': 'newcomers-welcome', 'intern_tasks': 'Interns will work on new tests for <a href="http://blends.debian.org/med/tasks/bio">Debian bioinformatics packages</a>.', 'intern_benefits': 'Interns will develop skills in quality assurance testing, learn Linux command-line tools, and gain knowledge in how Linux distributions like Debian package software.', 'community_benefits': 'Debian maintainers will spend less time tracking down bugs in newly released software.', 'new_contributors_welcome': True, }, # This says we're supposed to follow any and all redirects to other pages after the post # This will allow us to record a history of where the redirect went to follow=True, ) self.assertEqual(response.status_code, 403) self.assertContains( response, 'Not allowed to submit a project after its submission and approval deadline ({})' .format(current_round.lateprojects), status_code=403)
def test_project_soft_deadline(self): """ Mentors are told that projects are due one week before they're actually due. This is a "soft" project submission deadline. It's seven days before lateprojects - can be found by calling RoundPage.project_soft_deadline(). After the soft project submission deadline, the community CFP page shows project submission is closed. The community read-only page doesn't show the submit project button. *But* mentors can still submit if they know the URL. This allows us to deal with the few mentors who always miss the deadline. This tests submitting a project after the soft deadline succeeds: - Create a new RoundPage for the upcoming round with the project submission deadline passed - Create an approved community - Go to the community read-only page - There should not be a 'Submit Project' button - Submitting a project directly via the URL should still work """ scenario = scenarios.InternshipWeekScenario(week=14, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='lateprojects', days_after_today=6) participation = factories.ParticipationFactory( community=scenario.community, participating_round=current_round, approval_status=ApprovalStatus.APPROVED) # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get(reverse('community-cfp')) self.assertEqual(response.status_code, 200) self.assertContains( response, '<div class="card-header text-white bg-warning">Project and community CFP is currently closed</div>', html=True) # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get( reverse('community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, })) self.assertContains( response, '<div class="card-header text-white bg-warning">Project and community CFP is currently closed</div>', html=True) # Check that there is not a submit project button self.assertNotContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertContains(response, '<h2>Project Submission Deadline Passed</h2>', html=True) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) self.assertNotContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True) self.check_project_submission(scenario, current_round, participation)
def test_project_submission_no_community_participation(self): """ This tests submitting a project when the community is not signed up to participate - Create a new RoundPage for the upcoming round with project submission open - Do not add the old community to participate in the round - Go to the community read-only page - There should not be a 'Submit Project' button - Submitting a project directly via the URL should fail """ scenario = scenarios.InternshipWeekScenario(week=10, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='pingnew') # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get(reverse('community-cfp')) self.assertEqual(response.status_code, 200) # Check community CFP page to ensure the message about the CFP being closed is displayed response = self.client.get( reverse('community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, })) self.assertContains( response, '<span class="badge badge-pill badge-warning">Not Participating</span>', html=True) # Check that there is not a submit project button self.assertNotContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) self.assertNotContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True) # Submit project description self.client.force_login(scenario.mentor.account) response = self.client.post( project_submission_path, { 'approved_license': 'on', 'no_proprietary_software': 'on', 'longevity': '2Y', 'community_size': '20', 'short_title': 'Improve Debian bioinformatics packages test coverage', 'long_description': 'The Debian Med project has packaged a lot of <a href="http://blends.debian.org/med/tasks/bio">applications for bioinformatics</a>. You will be improving the test coverage of those packages.', 'minimum_system_requirements': 'A system running Debian Linux', 'contribution_tasks': 'Look at issues marked newcomers-welcome.', 'repository': 'https://salsa.debian.org/med-team', 'issue_tracker': 'https://bugs.debian.org/', 'newcomer_issue_tag': 'newcomers-welcome', 'intern_tasks': 'Interns will work on new tests for <a href="http://blends.debian.org/med/tasks/bio">Debian bioinformatics packages</a>.', 'intern_benefits': 'Interns will develop skills in quality assurance testing, learn Linux command-line tools, and gain knowledge in how Linux distributions like Debian package software.', 'community_benefits': 'Debian maintainers will spend less time tracking down bugs in newly released software.', 'new_contributors_welcome': True, }, # This says we're supposed to follow any and all redirects to other pages after the post # This will allow us to record a history of where the redirect went to follow=True, ) self.assertEqual(response.status_code, 404)
def test_community_participation_approval(self): """ This tests approving a community to participate in this round. - Create a new RoundPage for the upcoming round, with a pending community - Go to the community read-only page - Log in as an organizer - The community read-only page should have an 'Approve Community' and a 'Reject Community' button - Post to the Participation approval URL - This should redirect back to community read-only page - Participation should now marked as approved in the database - Coordinator receives email that the community was approved to participate - The community read-only page should now reflect that the community has been approved - Community status box should read 'Participating' - There should still be a way to submit projects Test home/templates/home/community_read_only.html: - Check: - The 'Participating' status is visible - Funding for 1 intern is visible - The 'Coordinate for This Community' button is visible to anyone who is not a coordinator - The 'Submit a Project Proposal' button is visible - The 'Submit an Outreachy Intern Project Proposal' heading is visible - The 'Community will participate' button is visible to a coordinator - The 'Community will not participate' button is visible to a coordinator """ scenario = scenarios.InternshipWeekScenario(week=10, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='pingnew') community_read_only_path = reverse( 'community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, }) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) coordinator_signup_path = reverse( 'coordinatorapproval-action', kwargs={ 'action': 'submit', 'community_slug': scenario.participation.community.slug, }) community_does_participate_path = reverse( 'participation-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) approve_participation_path = reverse( 'participation-action', kwargs={ 'action': 'approve', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) reject_participation_path = reverse( 'participation-action', kwargs={ 'action': 'reject', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) visitors = self.get_visitors_from_past_round(scenario) # Set up the community with a pending participation in the current round participation = factories.ParticipationFactory( community=scenario.community, participating_round=current_round, approval_status=ApprovalStatus.PENDING) sponsorship = factories.SponsorshipFactory( participation=participation, name='Software in the Public Interest - Debian', amount=13000) organizer_account = User.objects.get(is_staff=True) self.client.force_login(organizer_account) # Double check that the community read-only page has links to approve or reject response = self.client.get(community_read_only_path) self.assertEqual(response.status_code, 200) self.assertContains( response, '<span class="badge badge-pill badge-info">Pending Participation</span>', html=True) self.assertContains( response, '<input type="submit" class="btn btn-success m-2" value="Approve Community" />', html=True) self.assertContains( response, '<a href="{}" class="btn btn-warning m-2">Reject Community</a>'. format(reject_participation_path), html=True) # Approve the community response = self.client.post(approve_participation_path) self.assertEqual(response.status_code, 302) # Check the database status approved_participation = Participation.objects.get( community__slug=participation.community.slug, participating_round__slug=current_round.slug, approval_status=ApprovalStatus.APPROVED) # Check that the email to the community coordinator was sent self.assertEqual(len(mail.outbox), 1) self.assertEqual( mail.outbox[0].subject, '{} is participating in Outreachy!'.format( scenario.community.name)) self.assertEqual(mail.outbox[0].from_email, organizers) self.assertEqual(mail.outbox[0].to, scenario.community.get_coordinator_email_list()) self.assertIn( 'The Outreachy organizers have approved {} to participate in the current round of Outreachy!' .format(scenario.community.name), mail.outbox[0].body) # TODO: we should probably check that other information is correct, # like the round dates, but this is enough for now. # Check that the community read-only page reflects the database status for visitor_type, visitor in visitors: with self.subTest(visitor_type=visitor_type): self.client.logout() if visitor: self.client.force_login(visitor) response = self.client.get(community_read_only_path) self.assertEqual(response.status_code, 200) self.assertContains( response, '<span class="badge badge-pill badge-success">Participating</span>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-success">Funded</span>', html=True) self.assertContains( response, '<td>This community has funding for 2 interns.</td>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-warning">No Projects</span>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-info">Open to New Projects</span>', html=True) if visitor_type != 'coordinator': self.assertContains( response, '<a href="{}" class="btn btn-success">Coordinate for This Community</a>' .format(coordinator_signup_path), html=True) self.assertContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True)
def test_old_community_participation_signup(self): """ This tests submitting an older community to participate in this round. - Create a community that has been approved to participate in a past round - Create a new RoundPage for the upcoming round - Submit the community to participate in the round through the form - There should be an email sent to the Outreachy organizers about the participation - There should be a Participation object for this community in the current round marked as PENDING Test home/templates/home/community_read_only.html: - Check: - The 'Pending Participation' status is visible - Funding for 2 interns is visible - The 'Coordinate for This Community' button is visible to anyone who is not a coordinator - The 'Submit a Project Proposal' button is visible - The 'Submit an Outreachy Intern Project Proposal' heading is visible - The 'Community will participate' button is visible to a coordinator - The 'Community will not participate' button is visible to a coordinator """ scenario = scenarios.InternshipWeekScenario(week=10, community__name='Debian', community__slug='debian') current_round = factories.RoundPageFactory(start_from='pingnew') community_read_only_path = reverse( 'community-read-only', kwargs={ 'community_slug': scenario.participation.community.slug, }) project_submission_path = reverse( 'project-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) coordinator_signup_path = reverse( 'coordinatorapproval-action', kwargs={ 'action': 'submit', 'community_slug': scenario.participation.community.slug, }) community_does_participate_path = reverse( 'participation-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) community_does_not_participate_path = reverse( 'participation-action', kwargs={ 'action': 'withdraw', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }) sponsor_name = 'Software in the Public Interest - Debian' sponsor_amount = 13000 visitors = self.get_visitors_from_past_round(scenario) # There should not be a Participation for Debian in the current round yet with self.assertRaises(Participation.DoesNotExist): p = Participation.objects.get( community__slug=scenario.participation.community.slug, participating_round__slug=current_round.slug) self.client.force_login(scenario.coordinator.account) response = self.coordinator_signs_up_community_to_participate( reverse('participation-action', kwargs={ 'action': 'submit', 'round_slug': current_round.slug, 'community_slug': scenario.participation.community.slug, }), sponsor_name, sponsor_amount, ) self.assertEqual(response.status_code, 302) # Ensure the database reflects the community sign-up participation = Participation.objects.get( community__slug=scenario.participation.community.slug, participating_round__slug=current_round.slug, approval_status=ApprovalStatus.PENDING) sponsorship = Sponsorship.objects.get(participation=participation, coordinator_can_update=True, name=sponsor_name, amount=sponsor_amount, funding_secured=True) # Make sure the email to the Outreachy organizers was sent self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Approve community participation - Debian') self.assertEqual(mail.outbox[0].from_email, organizers) self.assertEqual(mail.outbox[0].to, [organizers]) self.assertIn(community_read_only_path, mail.outbox[0].body) self.assertIn('Number of interns funded: 2', mail.outbox[0].body) self.assertIn(sponsor_name, mail.outbox[0].body) self.assertIn(str(sponsor_amount), mail.outbox[0].body) for visitor_type, visitor in visitors: with self.subTest(visitor_type=visitor_type): self.client.logout() if visitor: self.client.force_login(visitor) response = self.client.get(community_read_only_path) self.assertEqual(response.status_code, 200) self.assertContains( response, '<span class="badge badge-pill badge-info">Pending Participation</span>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-success">Funded</span>', html=True) self.assertContains( response, '<td>This community has funding for 2 interns.</td>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-warning">No Projects</span>', html=True) self.assertContains( response, '<span class="badge badge-pill badge-info">Open to New Projects</span>', html=True) if visitor_type != 'coordinator': self.assertContains( response, '<a href="{}" class="btn btn-success">Coordinate for This Community</a>' .format(coordinator_signup_path), html=True) self.assertContains( response, '<h2>Submit an Outreachy Intern Project Proposal</h2>', html=True) self.assertContains( response, '<a class="btn btn-success" href="{}">Submit a Project Proposal</a>' .format(project_submission_path), html=True)