diff --git a/opentech/apply/funds/models/applications.py b/opentech/apply/funds/models/applications.py index e9aa7fe05c85db10f40eb0255d6b946cf4c690ff..bb7af8ebaed272ac944f9fdb821d00de2dd79b86 100644 --- a/opentech/apply/funds/models/applications.py +++ b/opentech/apply/funds/models/applications.py @@ -115,8 +115,8 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore FieldPanel('reviewers'), ReadOnlyPanel('get_workflow_name_display', heading="Workflow"), # Forms comes from parental key in models/forms.py - ReadOnlyInlinePanel('forms', help_text="Are copied from the parent fund."), - ReadOnlyInlinePanel('review_forms', help_text="Are copied from the parent fund."), + ReadOnlyInlinePanel('forms', help_text="Copied from the fund."), + ReadOnlyInlinePanel('review_forms', help_text="Copied from the fund."), ] edit_handler = TabbedInterface([ diff --git a/opentech/apply/funds/models/forms.py b/opentech/apply/funds/models/forms.py index 24650f12215d49ded3c8b044f2079786075d877f..40bf70d7308353816a2ba3b97bde955f0a31ba99 100644 --- a/opentech/apply/funds/models/forms.py +++ b/opentech/apply/funds/models/forms.py @@ -42,7 +42,7 @@ class AbstractRelatedForm(Orderable): def __eq__(self, other): try: - return self.fields == other.fields + return self.fields == other.fields and self.sort_order == other.sort_order except AttributeError: return False @@ -80,7 +80,7 @@ class AbstractRelatedReviewForm(Orderable): def __eq__(self, other): try: - return self.fields == other.fields + return self.fields == other.fields and self.sort_order == other.sort_order except AttributeError: return False diff --git a/opentech/apply/funds/tests/factories/models.py b/opentech/apply/funds/tests/factories/models.py index 12f7d836a15b9a4000c1e9c5bc30592d6cdaeeab..144d4ef4eac47e6e961a2017f4070f7e94fb2fb5 100644 --- a/opentech/apply/funds/tests/factories/models.py +++ b/opentech/apply/funds/tests/factories/models.py @@ -42,18 +42,25 @@ __all__ = [ 'LabSubmissionFactory', 'SealedRoundFactory', 'SealedSubmissionFactory', + 'workflow_for_stages', ] -class FundTypeFactory(wagtail_factories.PageFactory): +def workflow_for_stages(stages): + return list(FundType.WORKFLOW_CHOICES.keys())[stages - 1] + + +class AbstractApplicationFactory(wagtail_factories.PageFactory): class Meta: - model = FundType + abstract = True class Params: workflow_stages = 1 + title = factory.Faker('sentence') + # Will need to update how the stages are identified as Fund Page changes - workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOW_CHOICES.keys())[o.workflow_stages - 1]) + workflow_name = factory.LazyAttribute(lambda o: workflow_for_stages(o.workflow_stages)) @factory.post_generation def parent(self, create, extracted_parent, **parent_kwargs): @@ -85,7 +92,12 @@ class FundTypeFactory(wagtail_factories.PageFactory): ) -class RequestForPartnersFactory(FundTypeFactory): +class FundTypeFactory(AbstractApplicationFactory): + class Meta: + model = FundType + + +class RequestForPartnersFactory(AbstractApplicationFactory): class Meta: model = RequestForPartners @@ -156,16 +168,10 @@ class RoundBaseFormFactory(AbstractRelatedFormFactory): round = factory.SubFactory(RoundFactory) -class LabFactory(wagtail_factories.PageFactory): +class LabFactory(AbstractApplicationFactory): class Meta: model = LabType - class Params: - workflow_stages = 1 - number_forms = 1 - - # Will need to update how the stages are identified as Fund Page changes - workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOW_CHOICES.keys())[o.workflow_stages - 1]) lead = factory.SubFactory(StaffFactory) @factory.post_generation @@ -209,7 +215,7 @@ class ApplicationSubmissionFactory(factory.DjangoModelFactory): form_fields=factory.SelfAttribute('..form_fields'), ) page = factory.SubFactory(FundTypeFactory) - workflow_name = factory.LazyAttribute(lambda o: list(FundType.WORKFLOW_CHOICES.keys())[o.workflow_stages - 1]) + workflow_name = factory.LazyAttribute(lambda o: workflow_for_stages(o.workflow_stages)) round = factory.SubFactory( RoundFactory, workflow_name=factory.SelfAttribute('..workflow_name'), diff --git a/opentech/apply/funds/tests/test_admin_form.py b/opentech/apply/funds/tests/test_admin_form.py index 527cb2c63436d713fdfa95cb9071f6eb287406cd..8c4336f10dc74e8e2b6ba78b75f1bee723feb2bf 100644 --- a/opentech/apply/funds/tests/test_admin_form.py +++ b/opentech/apply/funds/tests/test_admin_form.py @@ -4,16 +4,22 @@ import factory from opentech.apply.funds.models import FundType -from .factories import ApplicationFormFactory, FundTypeFactory +from .factories import ApplicationFormFactory, FundTypeFactory, workflow_for_stages from opentech.apply.review.tests.factories import ReviewFormFactory -def formset_base(field, total, delete, factory): +def formset_base(field, total, delete, factory, same=False): base_data = { f'{field}-TOTAL_FORMS': total + delete, f'{field}-INITIAL_FORMS': 0, } - application_forms = factory.create_batch(total + delete) + + required_forms = total + delete + + if not same: + application_forms = factory.create_batch(required_forms) + else: + application_forms = [factory()] * required_forms deleted = 0 for i, form in enumerate(application_forms): @@ -28,11 +34,15 @@ def formset_base(field, total, delete, factory): return base_data -def form_data(number_forms=0, delete=0): - form_data = formset_base('forms', number_forms, delete, factory=ApplicationFormFactory) - review_form_data = formset_base('review_forms', number_forms, False, factory=ReviewFormFactory) +def form_data(number_forms=0, delete=0, stages=None, same_forms=False): + form_data = formset_base('forms', number_forms, delete, same=same_forms, factory=ApplicationFormFactory) + review_form_data = formset_base('review_forms', number_forms, False, same=same_forms, factory=ReviewFormFactory) form_data.update(review_form_data) - form_data.update(factory.build(dict, FACTORY_CLASS=FundTypeFactory)) + + fund_data = factory.build(dict, FACTORY_CLASS=FundTypeFactory) + fund_data['workflow_name'] = workflow_for_stages(stages or number_forms) + + form_data.update(fund_data) return form_data @@ -56,7 +66,7 @@ class TestWorkflowFormAdminForm(TestCase): self.assertTrue(form.is_valid(), form.errors.as_text()) def test_doesnt_validates_with_two_forms_one_stage(self): - form = self.submit_data(form_data(2)) + form = self.submit_data(form_data(2, stages=1)) self.assertFalse(form.is_valid()) self.assertTrue(form.errors['__all__']) formset_errors = form.formsets['forms'].errors @@ -64,3 +74,7 @@ class TestWorkflowFormAdminForm(TestCase): self.assertFalse(formset_errors[0]) # second form is too many self.assertTrue(formset_errors[1]['form']) + + def test_can_save_two_forms(self): + form = self.submit_data(form_data(2)) + self.assertTrue(form.is_valid()) diff --git a/opentech/apply/funds/tests/test_admin_views.py b/opentech/apply/funds/tests/test_admin_views.py new file mode 100644 index 0000000000000000000000000000000000000000..b065ad10da7a3acf82b850bb41ce1d33478af9da --- /dev/null +++ b/opentech/apply/funds/tests/test_admin_views.py @@ -0,0 +1,44 @@ +from django.test import TestCase +from django.urls import reverse + +from opentech.apply.users.tests.factories import SuperUserFactory +from opentech.apply.home.factories import ApplyHomePageFactory + +from .test_admin_form import form_data + + +class TestFundCreationView(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = SuperUserFactory() + cls.home = ApplyHomePageFactory() + + def create_page(self, forms=1, same_forms=False): + self.client.force_login(self.user) + url = reverse('wagtailadmin_pages:add', args=('funds', 'fundtype', self.home.id)) + + data = form_data(forms, same_forms=same_forms) + data['action-publish'] = True + + response = self.client.post(url, data=data, secure=True, follow=True) + self.assertContains(response, 'success') + + self.home.refresh_from_db() + fund = self.home.get_children().first() + + return fund.specific + + def test_can_create_fund(self): + fund = self.create_page() + self.assertEqual(fund.forms.count(), 1) + self.assertEqual(fund.review_forms.count(), 1) + + def test_can_create_multi_phase_fund(self): + fund = self.create_page(2) + self.assertEqual(fund.forms.count(), 2) + self.assertEqual(fund.review_forms.count(), 2) + + def test_can_create_multi_phase_fund_reuse_forms(self): + fund = self.create_page(2, same_forms=True) + self.assertEqual(fund.forms.count(), 2) + self.assertEqual(fund.review_forms.count(), 2) diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py index b4e043677ce42d10b2195fdd575ec1c0f59e84b6..a32420a93bf3aef0db8e267539c1ec49441d5adf 100644 --- a/opentech/apply/funds/tests/test_models.py +++ b/opentech/apply/funds/tests/test_models.py @@ -9,8 +9,6 @@ from django.core import mail from django.core.exceptions import ValidationError from django.test import TestCase, override_settings -from wagtail.core.models import Site - from opentech.apply.funds.models import ApplicationSubmission from opentech.apply.funds.blocks import EmailBlock, FullNameBlock from opentech.apply.funds.workflow import Request @@ -183,7 +181,6 @@ class TestRoundModelWorkflowAndForms(TestCase): @override_settings(ROOT_URLCONF='opentech.apply.urls') class TestFormSubmission(TestCase): def setUp(self): - self.site = Site.objects.first() self.User = get_user_model() self.email = 'test@test.com' @@ -191,8 +188,7 @@ class TestFormSubmission(TestCase): fund = FundTypeFactory() - self.site.root_page = fund - self.site.save() + self.site = fund.get_site() self.round_page = RoundFactory(parent=fund, now=True) self.lab_page = LabFactory(lead=self.round_page.lead) @@ -212,9 +208,10 @@ class TestFormSubmission(TestCase): request = make_request(user, data, method='post', site=self.site) - try: + if page.get_parent().id != self.site.root_page.id: + # Its a fund response = page.get_parent().serve(request) - except AttributeError: + else: response = page.serve(request) if not ignore_errors: diff --git a/opentech/apply/home/factories.py b/opentech/apply/home/factories.py index aa53b814af17d25b41e4a9a896fa12f3892c0df5..89628412d6bbb099533b39452a8c0ce1a54a037b 100644 --- a/opentech/apply/home/factories.py +++ b/opentech/apply/home/factories.py @@ -8,6 +8,20 @@ class ApplyHomePageFactory(wagtail_factories.PageFactory): class Meta: model = ApplyHomePage + @classmethod + def _create(cls, model_class, *args, **kwargs): + try: + # We cant use "django_get_or_create" in meta as wagtail factories wont respect it + return model_class.objects.get(slug=kwargs['slug']) + except model_class.DoesNotExist: + return super()._create(model_class, *args, **kwargs) + + @factory.post_generation + def parent(self, create, extracted_parent, **parent_kwargs): + if create and not self.get_parent(): + root = ApplyHomePage.get_first_root_node() + root.add_child(instance=self) + @factory.post_generation def site(self, create, extracted_site, **site_kwargs): if create: diff --git a/opentech/apply/home/wagtail_hooks.py b/opentech/apply/home/wagtail_hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..07ad3f9b3001bd54bbdee1ada3dbfd23defad8fc --- /dev/null +++ b/opentech/apply/home/wagtail_hooks.py @@ -0,0 +1,23 @@ +from wagtail.core import hooks + +from opentech.apply.users.groups import STAFF_GROUP_NAME + +from .models import ApplyHomePage + + +@hooks.register('construct_explorer_page_queryset') +def exclude_fund_pages(parent_page, pages, request): + # Don't allow editors to access the Apply pages in the explorer unless they know whats up + if not request.user.is_superuser: + pages = pages.not_descendant_of(ApplyHomePage.objects.first(), inclusive=True) + + return pages + + +@hooks.register('construct_main_menu') +def hide_explorer_menu_item_from_frank(request, menu_items): + if not request.user.is_superuser: + groups = list(request.user.groups.all()) + # If the user is only in the staff group they should never see the explorer menu item + if len(groups) == 1 and groups[0].name == STAFF_GROUP_NAME: + menu_items[:] = [item for item in menu_items if item.name != 'explorer'] diff --git a/opentech/public/utils/wagtail_hooks.py b/opentech/public/utils/wagtail_hooks.py index ec99c6dc25710552d8f0cf59acc69216fcde660c..51d7eb042495a8c5a31816cc8cab74d6b8896ce7 100644 --- a/opentech/public/utils/wagtail_hooks.py +++ b/opentech/public/utils/wagtail_hooks.py @@ -1,6 +1,6 @@ +from wagtail.core import hooks from wagtail.contrib.modeladmin.options import ModelAdminGroup, ModelAdmin, modeladmin_register - from opentech.public.news.models import NewsType from opentech.public.people.models import PersonType @@ -22,3 +22,9 @@ class TaxonomiesModelAdminGroup(ModelAdminGroup): modeladmin_register(TaxonomiesModelAdminGroup) + + +# Hide forms from the side menu, remove if adding public.forms back in +@hooks.register('construct_main_menu') +def hide_snippets_menu_item(request, menu_items): + menu_items[:] = [item for item in menu_items if item.name != 'forms']