diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py index b4e043677ce42d10b2195fdd575ec1c0f59e84b6..1d612a1e6c4bf3d3df87b05720a7dc83861a3138 100644 --- a/opentech/apply/funds/tests/test_models.py +++ b/opentech/apply/funds/tests/test_models.py @@ -199,9 +199,8 @@ class TestFormSubmission(TestCase): def submit_form(self, page=None, email=None, name=None, user=AnonymousUser(), ignore_errors=False): page = page or self.round_page - fields = page.get_form_fields() - data = CustomFormFieldsFactory.form_response(fields) + data = CustomFormFieldsFactory.form_response(page.form_fields) # Add our own data for field in page.forms.first().fields: diff --git a/opentech/apply/review/forms.py b/opentech/apply/review/forms.py index 74e1fdb150e0e450087adfd39a5ab530da185f9a..6ff588cd298bb4a9e14b66e7f497845748f97d51 100644 --- a/opentech/apply/review/forms.py +++ b/opentech/apply/review/forms.py @@ -3,25 +3,12 @@ import json from django import forms from django.core.exceptions import NON_FIELD_ERRORS -from opentech.apply.review.blocks import ScoredAnswerField from opentech.apply.review.options import NA from opentech.apply.stream_forms.forms import StreamBaseForm -from .blocks import RecommendationBlock from .models import Review -def get_recommendation_field(fields): - for field in fields: - try: - block = field.block - except AttributeError: - pass - else: - if isinstance(block, RecommendationBlock): - return field.id - - class MixedMetaClass(type(StreamBaseForm), type(forms.ModelForm)): pass @@ -76,7 +63,7 @@ class ReviewModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass) def save(self, commit=True): self.instance.score = self.calculate_score(self.cleaned_data) - self.instance.recommendation = int(self.cleaned_data[get_recommendation_field(self.instance.form_fields)]) + self.instance.recommendation = int(self.cleaned_data[self.instance.reccomendation_field.id]) self.instance.is_draft = self.draft_button_name in self.data self.instance.form_data = self.cleaned_data['form_data'] @@ -90,8 +77,8 @@ class ReviewModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass) def calculate_score(self, data): scores = list() - for field in self.get_score_fields(): - value = json.loads(data.get(field, '[null, null]')) + for field in self.instance.score_fields: + value = json.loads(data.get(field.id, '[null, null]')) try: score = int(value[1]) @@ -104,7 +91,4 @@ class ReviewModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass) try: return sum(scores) / len(scores) except ZeroDivisionError: - return 0 - - def get_score_fields(self): - return [field_name for field_name, field in self.fields.items() if isinstance(field, ScoredAnswerField)] + return NA diff --git a/opentech/apply/review/models.py b/opentech/apply/review/models.py index fff92008729805052d55e0e07e1fa2b8e1fbe176..e038f2a94afdb06e8cb6799cec187afb6ff8faec 100644 --- a/opentech/apply/review/models.py +++ b/opentech/apply/review/models.py @@ -13,12 +13,48 @@ from opentech.apply.review.options import YES, NO, MAYBE, RECOMMENDATION_CHOICES from opentech.apply.stream_forms.models import BaseStreamForm from opentech.apply.users.models import User -from .blocks import ReviewCustomFormFieldsBlock +from .blocks import ( + ReviewCustomFormFieldsBlock, + RecommendationBlock, + RecommendationCommentsBlock, + ScoreFieldBlock, +) + +class ReviewFormFieldsMixin(models.Model): + class Meta: + abstract = True + form_fields = StreamField(ReviewCustomFormFieldsBlock()) -class ReviewForm(models.Model): + @property + def score_fields(self): + return self._get_field_type(ScoreFieldBlock, many=True) + + @property + def reccomendation_field(self): + return self._get_field_type(RecommendationBlock) + + @property + def comment_field(self): + return self._get_field_type(RecommendationCommentsBlock) + + def _get_field_type(self, block_type, many=False): + fields = list() + for field in self.form_fields: + try: + if isinstance(field.block, block_type): + if many: + fields.append(field) + else: + return field + except AttributeError: + pass + if many: + return fields + + +class ReviewForm(ReviewFormFieldsMixin, models.Model): name = models.CharField(max_length=255) - form_fields = StreamField(ReviewCustomFormFieldsBlock()) panels = [ FieldPanel('name'), @@ -68,7 +104,7 @@ class ReviewQuerySet(models.QuerySet): return MAYBE -class Review(BaseStreamForm, AccessFormData, models.Model): +class Review(ReviewFormFieldsMixin, BaseStreamForm, AccessFormData, models.Model): submission = models.ForeignKey('funds.ApplicationSubmission', on_delete=models.CASCADE, related_name='reviews') revision = models.ForeignKey('funds.ApplicationRevision', on_delete=models.SET_NULL, related_name='reviews', null=True) author = models.ForeignKey( @@ -77,7 +113,6 @@ class Review(BaseStreamForm, AccessFormData, models.Model): ) form_data = JSONField(default=dict, encoder=DjangoJSONEncoder) - form_fields = StreamField(ReviewCustomFormFieldsBlock()) recommendation = models.IntegerField(verbose_name="Recommendation", choices=RECOMMENDATION_CHOICES, default=0) score = models.DecimalField(max_digits=10, decimal_places=1, default=0) diff --git a/opentech/apply/review/tests/factories/blocks.py b/opentech/apply/review/tests/factories/blocks.py index 82cd4a011bddef0e86086bd13b114caa92b1d9d9..31d6290a9dd01a88fdffd2cc908d7733fc926e0f 100644 --- a/opentech/apply/review/tests/factories/blocks.py +++ b/opentech/apply/review/tests/factories/blocks.py @@ -35,10 +35,12 @@ class ScoreFieldBlockFactory(FormFieldBlockFactory): @classmethod def make_form_answer(cls, params=dict()): - return { - 'description': factory.Faker('paragraph').generate(params), + defaults = { + 'description': factory.Faker('paragraph').generate({}), 'score': random.randint(1, 5), } + defaults.update(params) + return defaults ReviewFormFieldsFactory = StreamFieldUUIDFactory({ diff --git a/opentech/apply/review/tests/test_views.py b/opentech/apply/review/tests/test_views.py index 3104760ad13d2cedb1c00cef4543c578159d40b0..9b21dcabfaaee26120e487368c872e6d9f0477ca 100644 --- a/opentech/apply/review/tests/test_views.py +++ b/opentech/apply/review/tests/test_views.py @@ -4,7 +4,8 @@ from opentech.apply.funds.tests.factories.models import ApplicationSubmissionFac from opentech.apply.users.tests.factories import StaffFactory, UserFactory from opentech.apply.utils.testing.tests import BaseViewTestCase -from .factories import ReviewFactory, ReviewFormFieldsFactory +from .factories import ReviewFactory, ReviewFormFieldsFactory, ReviewFormFactory +from ..options import NA class StaffReviewsTestCase(BaseViewTestCase): @@ -83,24 +84,48 @@ class StaffReviewFormTestCase(BaseViewTestCase): self.assertEqual(response.context['title'], 'Update Review draft') def test_revision_captured_on_review(self): - field_ids = [f.id for f in self.submission.round.review_forms.first().fields] + form = self.submission.round.review_forms.first() - data = ReviewFormFieldsFactory.form_response(field_ids) + data = ReviewFormFieldsFactory.form_response(form.fields) self.post_page(self.submission, data, 'form') review = self.submission.reviews.first() self.assertEqual(review.revision, self.submission.live_revision) def test_can_submit_draft_review(self): - field_ids = [f.id for f in self.submission.round.review_forms.first().fields] + form = self.submission.round.review_forms.first() - data = ReviewFormFieldsFactory.form_response(field_ids) + data = ReviewFormFieldsFactory.form_response(form.fields) data['save_draft'] = True self.post_page(self.submission, data, 'form') review = self.submission.reviews.first() self.assertTrue(review.is_draft) self.assertIsNone(review.revision) + def test_score_calculated(self): + form = self.submission.round.review_forms.first() + score = 5 + + data = ReviewFormFieldsFactory.form_response(form.fields, { + field.id: {'score': score} + for field in form.form.score_fields + }) + + self.post_page(self.submission, data, 'form') + review = self.submission.reviews.first() + self.assertEqual(review.score, score) + + def test_no_score_is_NA(self): + form = ReviewFormFactory(form_fields__exclude__score=True) + review_form = self.submission.round.review_forms.first() + review_form.form = form + review_form.save() + + data = ReviewFormFieldsFactory.form_response(form.form_fields) + self.post_page(self.submission, data, 'form') + review = self.submission.reviews.first() + self.assertEqual(review.score, NA) + class UserReviewFormTestCase(BaseViewTestCase): user_factory = UserFactory diff --git a/opentech/apply/stream_forms/blocks.py b/opentech/apply/stream_forms/blocks.py index 0b850540c8f7008057098a7899d3ccdb6fdd52b3..117192eebe74114ebb19e9dcff4e67bbd5870215 100644 --- a/opentech/apply/stream_forms/blocks.py +++ b/opentech/apply/stream_forms/blocks.py @@ -47,8 +47,8 @@ class FormFieldBlock(StructBlock): return kwargs def get_field(self, struct_value): - return self.get_field_class(struct_value)( - **self.get_field_kwargs(struct_value)) + field_kwargs = self.get_field_kwargs(struct_value) + return self.get_field_class(struct_value)(**field_kwargs) def get_context(self, value, parent_context): context = super().get_context(value, parent_context) diff --git a/opentech/apply/stream_forms/testing/factories.py b/opentech/apply/stream_forms/testing/factories.py index fb7630a07135dcfbf24786cab258727a49d1257f..398a54607ebaab2b05a0c0869eee81e8c2a5509c 100644 --- a/opentech/apply/stream_forms/testing/factories.py +++ b/opentech/apply/stream_forms/testing/factories.py @@ -176,14 +176,19 @@ class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory): def build_form(self, data): extras = defaultdict(dict) + exclusions = [] for field, value in data.items(): # we dont care about position name, attr = field.split('__') - extras[name] = {attr: value} + if name == 'exclude': + exclusions.append(attr) + else: + extras[name] = {attr: value} + form_fields = {} for i, field in enumerate(self.factories): - if field == 'text_markup': + if field == 'text_markup' or field in exclusions: pass else: form_fields[f'{i}__{field}__'] = '' @@ -192,11 +197,11 @@ class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory): return form_fields - def form_response(self, fields): + def form_response(self, fields, field_values=dict()): data = { - field: factory.make_form_answer() - for field, factory in zip(fields, self.factories.values()) - if hasattr(factory, 'make_form_answer') + field.id: self.factories[field.block.name].make_form_answer(field_values.get(field, {})) + for field in fields + if hasattr(self.factories[field.block.name], 'make_form_answer') } return flatten_for_form(data)