diff --git a/hypha/apply/api/v1/screening/__init__.py b/hypha/apply/api/v1/screening/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/hypha/apply/api/v1/screening/filters.py b/hypha/apply/api/v1/screening/filters.py new file mode 100644 index 0000000000000000000000000000000000000000..51da1140a95ac74cdf6a2f4df84d23c0cfb8069c --- /dev/null +++ b/hypha/apply/api/v1/screening/filters.py @@ -0,0 +1,9 @@ +from django_filters import rest_framework as filters + +from hypha.apply.funds.models import ScreeningStatus + + +class ScreeningStatusFilter(filters.FilterSet): + class Meta: + model = ScreeningStatus + fields = ['yes', 'default'] diff --git a/hypha/apply/api/v1/screening/serializers.py b/hypha/apply/api/v1/screening/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..03f78157b8fe85a8b18464df9e6ed8c2a4a27521 --- /dev/null +++ b/hypha/apply/api/v1/screening/serializers.py @@ -0,0 +1,17 @@ +from rest_framework import serializers + +from hypha.apply.funds.models import ScreeningStatus + + +class ScreeningStatusListSerializer(serializers.ModelSerializer): + class Meta: + model = ScreeningStatus + fields = ('title', 'yes', 'default') + + +class ScreeningStatusSerializer(serializers.Serializer): + title = serializers.CharField() + + +class ScreeningStatusDefaultSerializer(serializers.Serializer): + yes = serializers.BooleanField() diff --git a/hypha/apply/api/v1/screening/views.py b/hypha/apply/api/v1/screening/views.py new file mode 100644 index 0000000000000000000000000000000000000000..baa152b081980c7d1463fbe7650eb62fd0c0fa50 --- /dev/null +++ b/hypha/apply/api/v1/screening/views.py @@ -0,0 +1,83 @@ +from django.shortcuts import get_object_or_404 + +from rest_framework import viewsets, permissions, mixins, status +from rest_framework.response import Response +from rest_framework.exceptions import ValidationError +from rest_framework.decorators import action + +from django_filters import rest_framework as filters + +from rest_framework_api_key.permissions import HasAPIKey + +from hypha.apply.funds.models import ScreeningStatus + +from ..pagination import StandardResultsSetPagination +from ..permissions import IsApplyStaffUser +from ..mixin import SubmissionNestedMixin + +from .filters import ScreeningStatusFilter +from .serializers import ( + ScreeningStatusListSerializer, + ScreeningStatusSerializer, + ScreeningStatusDefaultSerializer +) + + +class ScreeningStatusViewSet(viewsets.ReadOnlyModelViewSet): + permission_classes = ( + HasAPIKey | permissions.IsAuthenticated, HasAPIKey | IsApplyStaffUser, + ) + filter_backends = (filters.DjangoFilterBackend,) + filter_class = ScreeningStatusFilter + pagination_class = StandardResultsSetPagination + queryset = ScreeningStatus.objects.all() + serializer_class = ScreeningStatusListSerializer + + +class SubmissionScreeningStatusViewSet( + SubmissionNestedMixin, + mixins.ListModelMixin, + mixins.CreateModelMixin, + viewsets.GenericViewSet +): + permission_classes = ( + HasAPIKey | permissions.IsAuthenticated, HasAPIKey | IsApplyStaffUser, + ) + serializer_class = ScreeningStatusSerializer + + def get_queryset(self): + submission = self.get_submission_object() + return submission.screening_statuses.objects.all() + + def create(self, request, *args, **kwargs): + ser = self.get_serializer(data=request.data) + ser.is_valid(raise_exception=True) + submission = self.get_submission_object() + screening_status = get_object_or_404( + ScreeningStatus, title=ser.validated_data['title'] + ) + if not submission.screening_statuses.filter(default=True).exists(): + raise ValidationError({'detail': "Can't set screening status without default being set"}) + if ( + submission.screening_statuses.exists() and + submission.screening_statuses.first().yes != screening_status.yes + ): + raise ValidationError({'detail': "Can't set screening status for both yes and no"}) + submission.screening_statuses.add( + screening_status + ) + return Response(status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['post']) + def default(self, request, *args, **kwargs): + ser = ScreeningStatusDefaultSerializer(data=request.data) + ser.is_valid(raise_exception=True) + yes = ser.validated_data['yes'] + submission = self.get_submission_object() + if submission.screening_statuses.filter(default=False).exists(): + raise ValidationError({ + 'detail': "Can't set default as more than one screening status is already set." + }) + screening_status = ScreeningStatus.objects.get(default=True, yes=yes) + submission.screening_statuses.add(screening_status) + return Response(status=status.HTTP_201_CREATED) diff --git a/hypha/apply/api/v1/urls.py b/hypha/apply/api/v1/urls.py index e8a7635ece414386c0555cc36f8ec4018acd74b8..673a4daa9d727223db702cd54b68c64465726b7b 100644 --- a/hypha/apply/api/v1/urls.py +++ b/hypha/apply/api/v1/urls.py @@ -3,6 +3,7 @@ from rest_framework_nested import routers from hypha.apply.api.v1.determination.views import SubmissionDeterminationViewSet from hypha.apply.api.v1.review.views import SubmissionReviewViewSet +from hypha.apply.api.v1.screening.views import ScreeningStatusViewSet from .views import ( CommentViewSet, @@ -20,6 +21,7 @@ router = routers.SimpleRouter() router.register(r'submissions', SubmissionViewSet, basename='submissions') router.register(r'comments', CommentViewSet, basename='comments') router.register(r'rounds', RoundViewSet, basename='rounds') +router.register(r'screening_statuses', ScreeningStatusViewSet, basename='screenings') submission_router = routers.NestedSimpleRouter(router, r'submissions', lookup='submission') submission_router.register(r'actions', SubmissionActionViewSet, basename='submission-actions') diff --git a/hypha/apply/funds/admin.py b/hypha/apply/funds/admin.py index f120758328f903c6f9b1f529e2b0668c1e7551d9..66a63180a659afbcb8d982957f60b9cdc7d26f41 100644 --- a/hypha/apply/funds/admin.py +++ b/hypha/apply/funds/admin.py @@ -115,6 +115,7 @@ class ScreeningStatusAdmin(ModelAdmin): menu_icon = 'tag' list_display = ('title', 'yes', 'default') permission_helper_class = ScreeningStatusPermissionHelper + list_display = ('title', 'yes', 'default') class SealedRoundAdmin(BaseRoundAdmin): diff --git a/hypha/apply/funds/admin_views.py b/hypha/apply/funds/admin_views.py index 1991566d69bd9c9f33bf38597325f96c9b7240e1..8c23714954708b8547605972db51c967bd78ee1a 100644 --- a/hypha/apply/funds/admin_views.py +++ b/hypha/apply/funds/admin_views.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from wagtail.admin import messages from wagtail.admin.forms.pages import CopyForm from wagtail.admin.views.pages import get_valid_next_url_from_request -from wagtail.contrib.modeladmin.views import CreateView +from wagtail.contrib.modeladmin.views import CreateView, EditView from wagtail.core import hooks from wagtail.core.models import Page diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index 1b96744100affa7fe711eea2f6188a2f63b5246d..4b4d4edb78798acbe2fa33adc34b856776b746c5 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -95,7 +95,7 @@ class ScreeningSubmissionForm(ApplicationSubmissionModelForm): class Meta: model = ApplicationSubmission - fields = ('screening_status',) + fields = ('screening_statuses',) def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') diff --git a/hypha/apply/funds/management/commands/export_submissions_csv.py b/hypha/apply/funds/management/commands/export_submissions_csv.py index cf2bb59e083acaa7f5f3f1aa9048cf90dafbf42f..90ef23cf0e8d3cb2452087f0c84e3b290b988674 100644 --- a/hypha/apply/funds/management/commands/export_submissions_csv.py +++ b/hypha/apply/funds/management/commands/export_submissions_csv.py @@ -44,5 +44,5 @@ class Command(BaseCommand): submission_value = submission.value except KeyError: submission_value = 0 - + import ipdb; ipdb.set_trace() writer.writerow([submission.id, submission.title, submission.full_name, submission.email, submission_value, submission.duration, submission_reapplied, submission.stage, submission.phase, submission.screening_status, submission.submit_time.strftime('%Y-%m-%d'), submission_region, submission_country, submission_focus, submission_type]) diff --git a/hypha/apply/funds/migrations/0080_add_screening_statuses_field.py b/hypha/apply/funds/migrations/0080_add_screening_statuses_field.py new file mode 100644 index 0000000000000000000000000000000000000000..dfe45884b5248fde9ddc15a3520f834cd2e849b4 --- /dev/null +++ b/hypha/apply/funds/migrations/0080_add_screening_statuses_field.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.16 on 2020-10-21 10:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0079_add_reviewer_settings_for_submission_access'), + ] + + operations = [ + migrations.AddField( + model_name='applicationsubmission', + name='screening_statuses', + field=models.ManyToManyField(blank=True, related_name='submissions', to='funds.ScreeningStatus'), + ), + ] diff --git a/hypha/apply/funds/migrations/0081_migrate_screening_status_to_screening_statuses.py b/hypha/apply/funds/migrations/0081_migrate_screening_status_to_screening_statuses.py new file mode 100644 index 0000000000000000000000000000000000000000..61fbf831632d4fbc30df5a80f6aca17471b396f5 --- /dev/null +++ b/hypha/apply/funds/migrations/0081_migrate_screening_status_to_screening_statuses.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.16 on 2020-10-21 10:46 + +from django.db import migrations + + +def make_many_screening_statuses(apps, schema_editor): + """ + Adds the ScreeningStatus object in ApplicationSubmission.screening_status + to the many-to-many relationship in ApplicationSubmission.screening_statuses + """ + ApplicationSubmission = apps.get_model('funds', 'ApplicationSubmission') + + for submission in ApplicationSubmission.objects.all(): + if submission.screening_status: + submission.screening_statuses.add(submission.screening_status) + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0080_add_screening_statuses_field'), + ] + + operations = [ + migrations.RunPython(make_many_screening_statuses), + ] diff --git a/hypha/apply/funds/migrations/0082_remove_screening_status_field.py b/hypha/apply/funds/migrations/0082_remove_screening_status_field.py new file mode 100644 index 0000000000000000000000000000000000000000..80e7c7a7f5d14d42c103b2c7ed60b5bd65cf9785 --- /dev/null +++ b/hypha/apply/funds/migrations/0082_remove_screening_status_field.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.16 on 2020-10-21 10:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0081_migrate_screening_status_to_screening_statuses'), + ] + + operations = [ + migrations.RemoveField( + model_name='applicationsubmission', + name='screening_status', + ), + ] diff --git a/hypha/apply/funds/migrations/0083_auto_20201021_1129.py b/hypha/apply/funds/migrations/0083_auto_20201021_1129.py new file mode 100644 index 0000000000000000000000000000000000000000..2444167f0a6d5379b5855f5fc99827e1ea5bee9f --- /dev/null +++ b/hypha/apply/funds/migrations/0083_auto_20201021_1129.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.16 on 2020-10-21 11:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0082_remove_screening_status_field'), + ] + + operations = [ + migrations.AddField( + model_name='screeningstatus', + name='default', + field=models.BooleanField(default=False, verbose_name='Default Yes/No'), + ), + migrations.AddField( + model_name='screeningstatus', + name='yes', + field=models.BooleanField(default=False, verbose_name='Yes/No'), + ), + ] diff --git a/hypha/apply/funds/models/screening.py b/hypha/apply/funds/models/screening.py index 3c0084f5579b0530194b2b1beaa54d008a7bc2ea..f60be60178c49aa56e55f17628831e6ba1a6cf05 100644 --- a/hypha/apply/funds/models/screening.py +++ b/hypha/apply/funds/models/screening.py @@ -1,5 +1,4 @@ from django.db import models - from ..admin_forms import ScreeningStatusAdminForm diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index e2e6e892db49901e77c5aa2e8370823e0bf563e4..e0612b8e5eec2c791712e33da3d7f38577408795 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -453,12 +453,10 @@ class ApplicationSubmission( # Workflow inherited from WorkflowHelpers status = FSMField(default=INITIAL_STATE, protected=True) - screening_status = models.ForeignKey( + screening_statuses = models.ManyToManyField( 'funds.ScreeningStatus', - related_name='+', - on_delete=models.SET_NULL, - verbose_name='screening status', - null=True, + related_name='submissions', + blank=True ) is_draft = False diff --git a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html index 17956259bbbd07ea28684d8a68946d8fafd11b61..900d4bef0e5b58fdf5cdf1d482ca1e2612779dff 100644 --- a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html +++ b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html @@ -12,7 +12,7 @@ {% endif %} {% endif %} -{% if not object.screening_status %} +{% if not object.screening_statuses.exists %} <a data-fancybox data-src="#screen-application" class="button button--bottom-space button--primary button--full-width is-not-disabled" href="#">Screen application</a> {% endif %} diff --git a/hypha/apply/funds/templates/funds/includes/screening_form.html b/hypha/apply/funds/templates/funds/includes/screening_form.html index bb2a46df42f46c896fdac9c4b25060b2aa27edd5..6e63750888c2dc8944df48d6cb7e0cfc73920e42 100644 --- a/hypha/apply/funds/templates/funds/includes/screening_form.html +++ b/hypha/apply/funds/templates/funds/includes/screening_form.html @@ -1,7 +1,7 @@ {% if screening_form.should_show %} <div class="modal" id="screen-application"> <h4 class="modal__header-bar">Update status</h4> - <p>Current status: {{ object.screening_status }}</p> + <p>Current status: {% if object.screening_statuses.exists %}{{ object.screening_statuses }}{% endif %}</p> {% include 'funds/includes/delegated_form_base.html' with form=screening_form value='Screen'%} </div> {% endif %} diff --git a/hypha/apply/funds/templates/funds/includes/screening_status_block.html b/hypha/apply/funds/templates/funds/includes/screening_status_block.html index 6026ffa868edaa1b3eaf79ab283711ae911106b6..8600cd5f3b5d4199717aad73944a2fa7324bace2 100644 --- a/hypha/apply/funds/templates/funds/includes/screening_status_block.html +++ b/hypha/apply/funds/templates/funds/includes/screening_status_block.html @@ -1,6 +1,6 @@ <div class="sidebar__inner"> <h5>Screening Status</h5> <p> - {{ object.screening_status|default:"Awaiting Screen status" }} <a data-fancybox data-src="#screen-application" class="link link--secondary-change" href="#">Change</a> + {% if object.screening_statuses.exists %}{{ object.screening_statuses|default:"Awaiting Screen status" }}{% endif %} <a data-fancybox data-src="#screen-application" class="link link--secondary-change" href="#">Change</a> </p> </div> diff --git a/hypha/apply/funds/templates/funds/tables/table.html b/hypha/apply/funds/templates/funds/tables/table.html index e7048089c54b94069c7531f3d4b46c35972dde83..1a51c05275ef99ec81e7cae01bef92909230766e 100644 --- a/hypha/apply/funds/templates/funds/tables/table.html +++ b/hypha/apply/funds/templates/funds/tables/table.html @@ -34,7 +34,7 @@ {% endif %} </td> <td> - <strong>{{ submission.screening_status|default:"Awaiting Screen status" }}</strong> + <strong>{% if object.screening_statuses.exists %}{{ object.screening_statuses|default:"Awaiting Screen status" }}{% endif %}</strong> </td> <td> diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index 0358d7cead74096a058ad44631090b85b694987a..0904840ebd30aec4d6774167b3597a24383ad3f0 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -577,6 +577,7 @@ class ScreeningSubmissionView(DelegatedViewMixin, UpdateView): context_name = 'screening_form' def form_valid(self, form): + import ipdb; ipdb.set_trace() old = copy(self.get_object()) response = super().form_valid(form) # Record activity