diff --git a/hypha/apply/funds/migrations/0100_alter_applicationbase_labbase_approval_form.py b/hypha/apply/funds/migrations/0100_alter_applicationbase_labbase_approval_form.py new file mode 100644 index 0000000000000000000000000000000000000000..dd958a461b0b8f9b9443efe656621f30a5d90274 --- /dev/null +++ b/hypha/apply/funds/migrations/0100_alter_applicationbase_labbase_approval_form.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.13 on 2022-07-08 11:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('application_projects', '0053_projectapprovalform'), + ('funds', '0099_auto_20220629_1339'), + ] + + operations = [ + migrations.AddField( + model_name='applicationbase', + name='approval_form', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='funds', to='application_projects.projectapprovalform'), + ), + migrations.AddField( + model_name='labbase', + name='approval_form', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='labs', to='application_projects.projectapprovalform'), + ), + ] diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py index ef24ed5599e23b91357338a0fc0f383fd2ee7c9e..13837638bc5b5e5d94837eeab6297f921f8ee35b 100644 --- a/hypha/apply/funds/models/applications.py +++ b/hypha/apply/funds/models/applications.py @@ -71,6 +71,14 @@ class ApplicationBase(EmailForm, WorkflowStreamForm): # type: ignore blank=True, ) + approval_form = models.ForeignKey( + 'application_projects.ProjectApprovalForm', + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='funds', + ) + guide_link = models.URLField(blank=True, max_length=255, help_text=_('Link to the apply guide.')) slack_channel = models.CharField(blank=True, max_length=128, help_text=_('The slack #channel for notifications. If left empty, notifications will go to the default channel.')) @@ -108,6 +116,7 @@ class ApplicationBase(EmailForm, WorkflowStreamForm): # type: ignore return self.open_round.serve(request) content_panels = WorkflowStreamForm.content_panels + [ + FieldPanel('approval_form'), FieldPanel('reviewers', widget=forms.SelectMultiple(attrs={'size': '16'})), FieldPanel('guide_link'), FieldPanel('slack_channel'), @@ -409,6 +418,14 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm): # type: ig blank=True, ) + approval_form = models.ForeignKey( + 'application_projects.ProjectApprovalForm', + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='labs', + ) + guide_link = models.URLField(blank=True, max_length=255, help_text=_('Link to the apply guide.')) slack_channel = models.CharField(blank=True, max_length=128, help_text=_('The slack #channel for notifications.')) @@ -417,6 +434,7 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm): # type: ig subpage_types = [] # type: ignore content_panels = WorkflowStreamForm.content_panels + [ + FieldPanel('approval_form'), FieldPanel('lead'), FieldPanel('reviewers', widget=forms.SelectMultiple(attrs={'size': '16'})), FieldPanel('guide_link'), diff --git a/hypha/apply/funds/tests/factories/models.py b/hypha/apply/funds/tests/factories/models.py index 9da35a9b8fbf14a428183e84ddfc1036d963ee95..59727aa6127e314069bd332d97b752d419ba12fa 100644 --- a/hypha/apply/funds/tests/factories/models.py +++ b/hypha/apply/funds/tests/factories/models.py @@ -82,6 +82,7 @@ class AbstractApplicationFactory(wagtail_factories.PageFactory): # Will need to update how the stages are identified as Fund Page changes workflow_name = factory.LazyAttribute(lambda o: workflow_for_stages(o.workflow_stages)) + approval_form = factory.SubFactory('hypha.apply.projects.tests.factories.ProjectApprovalFormFactory') @factory.post_generation def forms(self, create, extracted, **kwargs): diff --git a/hypha/apply/funds/tests/test_admin_form.py b/hypha/apply/funds/tests/test_admin_form.py index 80119563409e97f5ae1f4c905c22da621af7579d..d90622fe487b524c23307f7d44f2c1ee8760ba97 100644 --- a/hypha/apply/funds/tests/test_admin_form.py +++ b/hypha/apply/funds/tests/test_admin_form.py @@ -54,6 +54,7 @@ def form_data(num_appl_forms=0, num_review_forms=0, num_determination_forms=0, n fund_data['workflow_name'] = workflow_for_stages(stages) form_data.update(fund_data) + form_data.update(approval_form='') return form_data diff --git a/hypha/apply/projects/admin.py b/hypha/apply/projects/admin.py index 711563188a21bdd91c2e29ea110956626a556f9e..2192a905029d65670e97d25449ee6d72694fc0f6 100644 --- a/hypha/apply/projects/admin.py +++ b/hypha/apply/projects/admin.py @@ -1,6 +1,7 @@ from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup -from .models import DocumentCategory +from .admin_views import CreateProjectApprovalFormView, EditProjectApprovalFormView +from .models import DocumentCategory, ProjectApprovalForm class DocumentCategoryAdmin(ModelAdmin): @@ -9,9 +10,26 @@ class DocumentCategoryAdmin(ModelAdmin): list_display = ('name', 'recommended_minimum',) +class ProjectApprovalFormAdmin(ModelAdmin): + model = ProjectApprovalForm + menu_icon = 'form' + list_display = ('name', 'used_by',) + create_view_class = CreateProjectApprovalFormView + edit_view_class = EditProjectApprovalFormView + + def used_by(self, obj): + rows = list() + for field in ('funds', 'labs',): + related = ', '.join(getattr(obj, f'{field}').values_list('title', flat=True)) + if related: + rows.append(related) + return ', '.join(rows) + + class ManageAdminGoup(ModelAdminGroup): menu_label = 'Manage' menu_icon = 'folder-open-inverse' items = ( DocumentCategoryAdmin, + ProjectApprovalFormAdmin, ) diff --git a/hypha/apply/projects/admin_views.py b/hypha/apply/projects/admin_views.py new file mode 100644 index 0000000000000000000000000000000000000000..1356717a7dd9f23fbb58361f67080391c709532c --- /dev/null +++ b/hypha/apply/projects/admin_views.py @@ -0,0 +1,37 @@ +from wagtail.contrib.modeladmin.views import CreateView, EditView + +from hypha.apply.utils.blocks import show_admin_form_error_messages + + +class CreateProjectApprovalFormView(CreateView): + + def get_form(self): + """ + Overriding this method to disable the single file block option from Project Approval Form. + Set 0 as max_number of single file can be added to make single file block option unavailable or disable. + """ + form = super(CreateProjectApprovalFormView, self).get_form() + form.fields['form_fields'].block.meta.block_counts = {'file': {'min_num': 0, 'max_num': 0}} + return form + + def form_invalid(self, form): + show_admin_form_error_messages(self.request, form) + return self.render_to_response(self.get_context_data(form=form)) + + +class EditProjectApprovalFormView(EditView): + + def get_form(self): + """ + Overriding this method to disable the single file block option from Project Approval Form. + Calculating the number of Single file blocks that exist in the instance already. + And set that count as max_number of single file block can be added to make single file option disable. + """ + form = super(EditProjectApprovalFormView, self).get_form() + single_file_count = sum(1 for block in self.get_instance().form_fields.raw_data if block['type'] == 'file') + form.fields['form_fields'].block.meta.block_counts = {'file': {'min_num': 0, 'max_num': single_file_count}} + return form + + def form_invalid(self, form): + show_admin_form_error_messages(self.request, form) + return self.render_to_response(self.get_context_data(form=form)) diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py index 7e0da4777bf2d0825fab2523746984f1a4caff3e..c7e3e2c7329481f3d62d8bd5bcd14e564e5bc41d 100644 --- a/hypha/apply/projects/forms/project.py +++ b/hypha/apply/projects/forms/project.py @@ -4,6 +4,7 @@ from django.db.models import Q from django.utils.translation import gettext_lazy as _ from hypha.apply.funds.models import ApplicationSubmission +from hypha.apply.stream_forms.forms import StreamBaseForm from hypha.apply.users.groups import STAFF_GROUP_NAME from ..models.project import COMMITTED, Approval, Contract, PacketFile, Project @@ -76,30 +77,38 @@ class CreateApprovalForm(forms.ModelForm): return by -class ProjectApprovalForm(forms.ModelForm): +class MixedMetaClass(type(StreamBaseForm), type(forms.ModelForm)): + pass + + +class ProjectApprovalForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass): class Meta: fields = [ 'title', - 'value', - 'proposed_start', - 'proposed_end', ] model = Project widgets = { - 'title': forms.TextInput, - 'proposed_end': forms.DateInput, - 'proposed_start': forms.DateInput, + 'title': forms.HiddenInput() } def __init__(self, *args, extra_fields=None, **kwargs): super().__init__(*args, **kwargs) - if extra_fields: - self.fields = { - **self.fields, - **extra_fields, - } + + def clean(self): + cleaned_data = super().clean() + cleaned_data['form_data'] = { + key: value + for key, value in cleaned_data.items() + if key not in self._meta.fields + } + return cleaned_data def save(self, *args, **kwargs): + self.instance.form_data = { + field: self.cleaned_data[field] + for field in self.instance.question_field_ids + if field in self.cleaned_data + } self.instance.user_has_updated_details = True return super().save(*args, **kwargs) diff --git a/hypha/apply/projects/migrations/0053_projectapprovalform.py b/hypha/apply/projects/migrations/0053_projectapprovalform.py new file mode 100644 index 0000000000000000000000000000000000000000..6b5d8c4887a61ce0514773c01a83c958af686888 --- /dev/null +++ b/hypha/apply/projects/migrations/0053_projectapprovalform.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.13 on 2022-07-08 11:28 + +from django.db import migrations, models +import hypha.apply.stream_forms.blocks +import hypha.apply.stream_forms.models +import wagtail.core.blocks +import wagtail.core.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('application_projects', '0052_alter_project_form_fields'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectApprovalForm', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('form_fields', wagtail.core.fields.StreamField([('text_markup', wagtail.core.blocks.RichTextBlock(group='Custom', label='Section text')), ('header_markup', wagtail.core.blocks.StructBlock([('heading_text', wagtail.core.blocks.CharBlock(form_classname='title', required=True)), ('size', wagtail.core.blocks.ChoiceBlock(choices=[('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4')]))], group='Custom', label='Section header')), ('char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('multi_inputs_char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False)), ('number_of_inputs', wagtail.core.blocks.IntegerBlock(default=2, label='Max number of inputs')), ('add_button_text', wagtail.core.blocks.CharBlock(default='Add new item', required=False))], group='Fields')), ('text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('number', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('checkbox', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('radios', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('dropdown', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('checkboxes', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Checkbox')))], group='Fields')), ('date', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateBlock(required=False))], group='Fields')), ('time', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TimeBlock(required=False))], group='Fields')), ('datetime', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateTimeBlock(required=False))], group='Fields')), ('image', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('multi_file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('group_toggle', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(default=True, label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice'), help_text='Please create only two choices for toggle. First choice will revel the group and the second hide it. Additional choices will be ignored.'))], group='Custom')), ('group_toggle_end', hypha.apply.stream_forms.blocks.GroupToggleEndBlock(group='Custom'))])), + ], + bases=(hypha.apply.stream_forms.models.BaseStreamForm, models.Model), + ), + ] diff --git a/hypha/apply/projects/models/__init__.py b/hypha/apply/projects/models/__init__.py index 2aeab4cc6bd89e11b3468d39c662d35c8381deb7..7794e9e2cec0dd0c1d67c447375c54744f5b528a 100644 --- a/hypha/apply/projects/models/__init__.py +++ b/hypha/apply/projects/models/__init__.py @@ -6,6 +6,7 @@ from .project import ( DocumentCategory, PacketFile, Project, + ProjectApprovalForm, ProjectSettings, ) from .report import Report, ReportConfig, ReportPrivateFiles, ReportVersion @@ -13,6 +14,7 @@ from .vendor import BankInformation, DueDiligenceDocument, Vendor __all__ = [ 'Project', + 'ProjectApprovalForm', 'ProjectSettings', 'Approval', 'Contract', diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py index e3c470cf519a62352fb126278bf48d9becb95453..10e5159f203d946c80dddf3c53f09e14ba938c6a 100644 --- a/hypha/apply/projects/models/project.py +++ b/hypha/apply/projects/models/project.py @@ -16,6 +16,7 @@ from django.dispatch.dispatcher import receiver from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel from wagtail.contrib.settings.models import BaseSetting, register_setting from wagtail.core.fields import StreamField @@ -372,6 +373,19 @@ class Project(BaseStreamForm, AccessFormData, models.Model): # self.save(update_fields=['sent_to_compliance_at']) +class ProjectApprovalForm(BaseStreamForm, models.Model): + name = models.CharField(max_length=255) + form_fields = StreamField(FormFieldsBlock()) + + panels = [ + FieldPanel('name'), + StreamFieldPanel('form_fields'), + ] + + def __str__(self): + return self.name + + @register_setting class ProjectSettings(BaseSetting): compliance_email = models.TextField("Compliance Email") diff --git a/hypha/apply/projects/templates/application_projects/project_approval_form.html b/hypha/apply/projects/templates/application_projects/project_approval_form.html new file mode 100644 index 0000000000000000000000000000000000000000..8520b225d9c2a2e2a4d861e873aac9898ee9c2d4 --- /dev/null +++ b/hypha/apply/projects/templates/application_projects/project_approval_form.html @@ -0,0 +1,55 @@ +{% extends "base-apply.html" %} +{% load i18n static %} +{% block title %}Editing: {{object.title }}{% endblock %} +{% block content %} +<div class="admin-bar"> + <div class="admin-bar__inner"> + <h2 class="heading heading--no-margin">{% trans "Editing" %}: {{ object.title }}</h2> + </div> +</div> + +{% include "forms/includes/form_errors.html" with form=form %} + +<div class="wrapper wrapper--light-grey-bg wrapper--form wrapper--sidebar"> + <div class="wrapper--sidebar--inner"> + <form class="form application-form" action="" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form.media }} + + {% for field in form %} + {% if field.field %} + {% if field.field.multi_input_field %} + {% include "forms/includes/multi_input_field.html" %} + {% else %} + {% include "forms/includes/field.html" %} + {% endif %} + {% else %} + {{ field.block }} + {% endif %} + {% endfor %} + + {# Hidden fields needed e.g. for django-file-form. See `StreamBaseForm.hidden_fields` #} + {% for hidden_field in form.hidden_fields %} + {{ hidden_field }} + {% endfor %} + + {% trans "Save draft" as save_draft %} + {% for button_name, button_type, button_value in buttons %} + <button class="button button--submit button--top-space button--{{ button_type }}" type="submit" name="{{ button_name }}" {% if button_value == save_draft %}formnovalidate{% endif %}>{{ button_value }}</button> + {% endfor %} + </form> + </div> +</div> + +{% endblock %} + +{% block extra_js %} + <script src="{% static 'js/apply/list-input-files.js' %}"></script> + <script src="{% static 'js/apply/tinymce-word-count.js' %}"></script> + <script src="{% static 'js/apply/multi-input-fields.js' %}"></script> + <script src="{% static 'js/apply/submission-form-copy.js' %}"></script> + <script src="{% static 'js/apply/application-form-links-new-window.js' %}"></script> + {% if not show_all_group_fields %} + <script src="{% static 'js/apply/form-group-toggle.js' %}"></script> + {% endif %} +{% endblock %} diff --git a/hypha/apply/projects/tests/factories.py b/hypha/apply/projects/tests/factories.py index a1e710afb1f5836c98606f814241e4dafc1d40db..50c7bee415d657efd57f2c4f30941352a4be8d17 100644 --- a/hypha/apply/projects/tests/factories.py +++ b/hypha/apply/projects/tests/factories.py @@ -5,6 +5,10 @@ from dateutil.relativedelta import relativedelta from django.utils import timezone from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory +from hypha.apply.stream_forms.testing.factories import ( + FormDataFactory, + FormFieldsBlockFactory, +) from hypha.apply.users.tests.factories import StaffFactory, UserFactory from ..models.payment import Invoice, InvoiceDeliverable, SupportingDocument @@ -16,6 +20,7 @@ from ..models.project import ( DocumentCategory, PacketFile, Project, + ProjectApprovalForm, ) from ..models.report import Report, ReportConfig, ReportVersion @@ -53,6 +58,18 @@ class DocumentCategoryFactory(factory.django.DjangoModelFactory): model = DocumentCategory +class ProjectApprovalFormFactory(factory.django.DjangoModelFactory): + class Meta: + model = ProjectApprovalForm + + name = factory.Faker('word') + form_fields = FormFieldsBlockFactory + + +class ProjectApprovalFormDataFactory(FormDataFactory): + field_factory = FormFieldsBlockFactory + + class ProjectFactory(factory.django.DjangoModelFactory): submission = factory.SubFactory(ApplicationSubmissionFactory) user = factory.SubFactory(UserFactory) @@ -65,6 +82,12 @@ class ProjectFactory(factory.django.DjangoModelFactory): is_locked = False + form_fields = FormFieldsBlockFactory + form_data = factory.SubFactory( + ProjectApprovalFormDataFactory, + form_fields=factory.SelfAttribute('..form_fields'), + ) + class Meta: model = Project diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py index 66f2b283f5a1065fae3e26fcbf7b8858f73f1335..b436eb319248176f131024e64ea9a093b193ed32 100644 --- a/hypha/apply/projects/views/project.py +++ b/hypha/apply/projects/views/project.py @@ -11,6 +11,7 @@ from django.shortcuts import get_object_or_404, redirect from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator +from django.utils.functional import cached_property from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ from django.views import View @@ -27,6 +28,7 @@ from django_tables2 import SingleTableMixin from hypha.apply.activity.messaging import MESSAGES, messenger from hypha.apply.activity.views import ActivityContextMixin, CommentFormView +from hypha.apply.stream_forms.models import BaseStreamForm from hypha.apply.users.decorators import ( approver_required, staff_or_finance_required, @@ -589,9 +591,14 @@ class ProjectDetailPDFView(SingleObjectMixin, View): @method_decorator(staff_required, name='dispatch') -class ProjectApprovalEditView(UpdateView): - form_class = ProjectApprovalForm +class ProjectApprovalEditView(BaseStreamForm, UpdateView): + submission_form_class = ProjectApprovalForm model = Project + template_name = 'application_projects/project_approval_form.html' + + def buttons(self): + yield ('submit', 'primary', _('Submit')) + # yield ('save', 'white', _('Save draft')) def dispatch(self, request, *args, **kwargs): project = self.get_object() @@ -600,7 +607,48 @@ class ProjectApprovalEditView(UpdateView): return redirect(project) return super().dispatch(request, *args, **kwargs) + @cached_property + def approval_form(self): + if self.object.get_defined_fields(): + approval_form = self.object + else: + approval_form = self.object.submission.page.specific.approval_form + + return approval_form + + def get_context_data(self, **kwargs): + return super().get_context_data( + title=self.object.title, + buttons=self.buttons(), + **kwargs + ) + + def get_defined_fields(self): + approval_form = self.object.submission.get_from_parent('approval_form') + if approval_form: + return approval_form.form_fields + return self.object.get_defined_fields() + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + + if self.approval_form: + fields = self.approval_form.get_form_fields() + else: + fields = {} + + kwargs['extra_fields'] = fields + kwargs['initial'].update(self.object.raw_data) + return kwargs + def form_valid(self, form): + try: + form_fields = self.approval_form.form_fields + except AttributeError: + form_fields = [] + + form.instance.form_fields = form_fields + return super().form_valid(form)