Skip to content

Commit

Permalink
Merge pull request #2906 from HyphaApp/feature-project-approval-form
Browse files Browse the repository at this point in the history
Re add the stream field form for PAF
  • Loading branch information
frjo authored Jul 14, 2022
2 parents d9f9cc3 + 02193a4 commit 623a9c0
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -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'),
),
]
18 changes: 18 additions & 0 deletions hypha/apply/funds/models/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.'))
Expand Down Expand Up @@ -108,6 +116,7 @@ def serve(self, request):
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'),
Expand Down Expand Up @@ -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.'))
Expand All @@ -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'),
Expand Down
1 change: 1 addition & 0 deletions hypha/apply/funds/tests/factories/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Params:

# 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):
Expand Down
1 change: 1 addition & 0 deletions hypha/apply/funds/tests/test_admin_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
20 changes: 19 additions & 1 deletion hypha/apply/projects/admin.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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,
)
37 changes: 37 additions & 0 deletions hypha/apply/projects/admin_views.py
Original file line number Diff line number Diff line change
@@ -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))
33 changes: 21 additions & 12 deletions hypha/apply/projects/forms/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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
Expand Down Expand Up @@ -76,30 +77,38 @@ def clean_by(self):
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)

Expand Down
26 changes: 26 additions & 0 deletions hypha/apply/projects/migrations/0053_projectapprovalform.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
2 changes: 2 additions & 0 deletions hypha/apply/projects/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
DocumentCategory,
PacketFile,
Project,
ProjectApprovalForm,
ProjectSettings,
)
from .report import Report, ReportConfig, ReportPrivateFiles, ReportVersion
from .vendor import BankInformation, DueDiligenceDocument, Vendor

__all__ = [
'Project',
'ProjectApprovalForm',
'ProjectSettings',
'Approval',
'Contract',
Expand Down
14 changes: 14 additions & 0 deletions hypha/apply/projects/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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

Expand Down Expand Up @@ -372,6 +373,19 @@ def program_project_id(self):
# 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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 %}
Loading

0 comments on commit 623a9c0

Please sign in to comment.