Skip to content
This repository has been archived by the owner on Feb 13, 2022. It is now read-only.

Commit

Permalink
Merge branch 'release/6.9.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaitlyn Crawford committed Aug 1, 2018
2 parents 2672415 + 7ab4fbd commit 543ae39
Show file tree
Hide file tree
Showing 11 changed files with 473 additions and 78 deletions.
27 changes: 27 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
CHANGE LOG
==========
6.9.6
-----
- Bug fix: convert lists to strings when getting MoloSurveySubmission data

6.9.5
-----
- Bug fix: remove model validation for MoloSurveyFormField and move it to BaseMoloSurveyForm

6.9.4
-----
- Bug fix: converting an already unicode str to unicode error in the clean_name attr

6.9.3
-----
- Bug fix: unicode support for SurveyAbstractFormField's clean_name attribute

6.9.1
-----
- Bug fix: add pk to session stored survey answers to prevent duplicate dict keys

6.9.0
-----
- Override choice field to textfield from charfied with 512 limit

6.8.2
-----
- Bug Fix: redirect to translated surveys when another language is selected

6.8.1
-----
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.8.1
6.9.6
85 changes: 84 additions & 1 deletion molo/surveys/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from .widgets import NaturalDateInput


CHARACTER_COUNT_CHOICE_LIMIT = 512


class CharacterCountWidget(forms.TextInput):
class Media:
js = ('js/widgets/character_count.js',)
Expand All @@ -31,6 +34,50 @@ def render(self, name, value, attrs=None):
)


class CharacterCountMixin(object):
max_length = CHARACTER_COUNT_CHOICE_LIMIT

def __init__(self, *args, **kwargs):
self.max_length = kwargs.pop('max_length', self.max_length)
super(CharacterCountMixin, self).__init__(*args, **kwargs)

self.error_messages['max_length'] = _(
'This field can not be more than {max_length} characters long'
).format(max_length=self.max_length)

def validate(self, value):
super(CharacterCountMixin, self).validate(value)
if len(value) > self.max_length:
raise ValidationError(
self.error_messages['max_length'],
code='max_length', params={'value': value},
)


class CharacterCountMultipleChoiceField(
CharacterCountMixin, forms.MultipleChoiceField
):
""" Limit character count for Multi choice fields """


class CharacterCountChoiceField(
CharacterCountMixin, forms.ChoiceField
):
""" Limit character count for choice fields """


class CharacterCountCheckboxSelectMultiple(
CharacterCountMixin, forms.CheckboxSelectMultiple
):
""" Limit character count for checkbox fields """


class CharacterCountCheckboxInput(
CharacterCountMixin, forms.CheckboxInput
):
""" Limit character count for checkbox fields """


class MultiLineWidget(forms.Textarea):
def render(self, name, value, attrs=None):
return format_html(
Expand Down Expand Up @@ -63,6 +110,28 @@ def create_datetime_field(self, field, options):
def create_positive_number_field(self, field, options):
return forms.DecimalField(min_value=0, **options)

def create_dropdown_field(self, field, options):
options['choices'] = map(
lambda x: (x.strip(), x.strip()),
field.choices.split(','))
return CharacterCountChoiceField(**options)

def create_radio_field(self, field, options):
options['choices'] = map(
lambda x: (x.strip(), x.strip()),
field.choices.split(','))
return CharacterCountChoiceField(widget=forms.RadioSelect, **options)

def create_checkboxes_field(self, field, options):
options['choices'] = [
(x.strip(), x.strip()) for x in field.choices.split(',')
]
options['initial'] = [
x.strip() for x in field.default_value.split(',')
]
return CharacterCountMultipleChoiceField(
widget=forms.CheckboxSelectMultiple, **options)

@property
def formfields(self):
'''
Expand All @@ -76,7 +145,6 @@ def formfields(self):

for field in self.fields:
options = self.get_field_options(field)

if field.field_type in self.FIELD_TYPES:
method = getattr(self,
self.FIELD_TYPES[field.field_type].__name__)
Expand Down Expand Up @@ -163,6 +231,7 @@ def save(self, *args, **kwargs):


class BaseMoloSurveyForm(WagtailAdminPageForm):

def clean(self):
cleaned_data = super(BaseMoloSurveyForm, self).clean()

Expand All @@ -183,13 +252,27 @@ def clean(self):
'Options: a True and False'),
)
elif data['field_type'] in VALID_SKIP_LOGIC:
choices_length = 0
for i, logic in enumerate(data['skip_logic']):
if not logic.value['choice']:
self.add_stream_field_error(
i,
'choice',
_('This field is required.'),
)
else:
choices_length += len(logic.value['choice'])

if choices_length > CHARACTER_COUNT_CHOICE_LIMIT:
err = 'The combined choices\' maximum characters ' \
'limit has been exceeded ({max_limit} '\
'character(s)).'

self.add_form_field_error(
'field_type',
_(err).format(
max_limit=CHARACTER_COUNT_CHOICE_LIMIT),
)

for i, logic in enumerate(data['skip_logic']):
if logic.value['skip_logic'] == SkipState.SURVEY:
Expand Down
20 changes: 20 additions & 0 deletions molo/surveys/migrations/0027_override-512-char-limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-07-12 10:37
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('surveys', '0026_remove_molosurveypageview_tag'),
]

operations = [
migrations.AlterField(
model_name='molosurveyformfield',
name='choices',
field=models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes,radio and dropdown.', verbose_name='choices'),
),
]
70 changes: 56 additions & 14 deletions molo/surveys/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-

import json
import datetime

from unidecode import unidecode
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.paginator import EmptyPage, PageNotAnInteger
Expand All @@ -14,6 +16,9 @@
from django.shortcuts import redirect, render
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.utils.text import slugify
from django.utils.encoding import smart_str
from django.utils.six import text_type
from modelcluster.fields import ParentalKey
from molo.core.blocks import MarkDownBlock
from molo.core.models import (
Expand Down Expand Up @@ -49,6 +54,7 @@
MoloSurveyForm,
PersonalisableMoloSurveyForm,
SurveysFormBuilder,
CHARACTER_COUNT_CHOICE_LIMIT,
)
from .rules import ( # noqa
ArticleTagRule,
Expand All @@ -69,6 +75,17 @@
FooterPage.parent_page_types += ['surveys.TermsAndConditionsIndexPage']


class SurveyAbstractFormField(AbstractFormField):
class Meta:
abstract = True
ordering = ['sort_order']

@property
def clean_name(self):
return str(slugify(text_type(unidecode(
'{} {}'.format(self.pk, smart_str(self.label))))))


class TermsAndConditionsIndexPage(TranslatablePageMixinNotRoutable, MoloPage):
parent_page_types = ['surveys.SurveysIndexPage']
subpage_types = ['core.Footerpage']
Expand Down Expand Up @@ -286,6 +303,13 @@ def serve_questions(self, request):
When the last step is submitted correctly, the whole form is saved in
the DB.
"""
context = self.get_context(request)
# this will only return a page if there is a translation
page = context['page'].get_translation_for(
locale=request.LANGUAGE_CODE, site=request.site)
if page:
# if there is a translation, redirect to the translated page
return redirect(page.url)
survey_data = self.load_data(request)

paginator = SkipLogicPaginator(
Expand Down Expand Up @@ -366,7 +390,7 @@ def serve_questions(self, request):
# Create empty form for non-POST requests
form_class = self.get_form_class_for_step(step)
form = form_class(page=self, user=request.user)
context = self.get_context(request)

context['form'] = form
context['fields_step'] = step
context['is_intermediate_step'] = step.possibly_has_next()
Expand Down Expand Up @@ -453,7 +477,7 @@ class Meta:
abstract = True


surveys_models.AbstractFormField.panels.append(FieldPanel('page_break'))
SurveyAbstractFormField.panels.append(FieldPanel('page_break'))


class AdminLabelMixin(models.Model):
Expand All @@ -468,8 +492,8 @@ class Meta:
abstract = True


surveys_models.AbstractFormField.panels.append(FieldPanel('admin_label'))
surveys_models.AbstractFormField._meta.get_field('label').verbose_name = (
SurveyAbstractFormField.panels.append(FieldPanel('admin_label'))
SurveyAbstractFormField._meta.get_field('label').verbose_name = (
'Question'
)

Expand Down Expand Up @@ -528,15 +552,26 @@ def save(self, *args, **kwargs):


class MoloSurveyFormField(SkipLogicMixin, AdminLabelMixin,
QuestionPaginationMixin, AbstractFormField):
AbstractFormField.FORM_FIELD_CHOICES += (
QuestionPaginationMixin, SurveyAbstractFormField):

page = ParentalKey(MoloSurveyPage, related_name='survey_form_fields')
SurveyAbstractFormField.FORM_FIELD_CHOICES += (
('positive_number', _("Positive Number")),)
choices = models.TextField(
verbose_name=_('choices'),
blank=True,
help_text=_(
'Comma separated list of choices. Only applicable in checkboxes,'
'radio and dropdown.')
)
field_type = models.CharField(
verbose_name=_('field type'),
max_length=16, choices=AbstractFormField.FORM_FIELD_CHOICES)
page = ParentalKey(MoloSurveyPage, related_name='survey_form_fields')
max_length=16, choices=SurveyAbstractFormField.FORM_FIELD_CHOICES)

class Meta(AbstractFormField.Meta):
def __init__(self, *args, **kwargs):
super(MoloSurveyFormField, self).__init__(*args, **kwargs)

class Meta(SurveyAbstractFormField.Meta):
pass

def clean(self):
Expand All @@ -551,7 +586,7 @@ def clean(self):
{'default_value': ["Must be a valid date", ]})


surveys_models.AbstractFormField.panels[4] = SkipLogicStreamPanel('skip_logic')
SurveyAbstractFormField.panels[4] = SkipLogicStreamPanel('skip_logic')


class MoloSurveySubmission(surveys_models.AbstractFormSubmission):
Expand All @@ -569,6 +604,10 @@ class MoloSurveySubmission(surveys_models.AbstractFormSubmission):

def get_data(self):
form_data = super(MoloSurveySubmission, self).get_data()
for key, value in form_data.items():
# Convert lists to strings so they display properly in the view
if isinstance(value, list):
form_data[key] = u', '.join(value)
form_data.update({
'username': self.user.username if self.user else 'Anonymous',
})
Expand Down Expand Up @@ -675,7 +714,7 @@ def serve(self, request, *args, **kwargs):

class PersonalisableSurveyFormField(SkipLogicMixin, AdminLabelMixin,
QuestionPaginationMixin,
AbstractFormField):
SurveyAbstractFormField):
"""
Form field that has a segment assigned.
"""
Expand All @@ -691,12 +730,15 @@ class PersonalisableSurveyFormField(SkipLogicMixin, AdminLabelMixin,

panels = [
FieldPanel('segment')
] + AbstractFormField.panels
] + SurveyAbstractFormField.panels

def __init__(self, *args, **kwargs):
super(PersonalisableSurveyFormField, self).__init__(*args, **kwargs)

def __str__(self):
return '{} - {}'.format(self.page, self.label)

class Meta(AbstractFormField.Meta):
class Meta(SurveyAbstractFormField.Meta):
verbose_name = _('personalisable form field')

def clean(self):
Expand Down
9 changes: 9 additions & 0 deletions molo/surveys/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ def test_user(self, request, user=None):
if self.operator == self.EQUALS:
return set(python_value) == set(user_response)

if isinstance(python_value, list) \
and isinstance(user_response, six.string_types):
user_response = user_response.split(u', ')
if self.operator == self.CONTAINS:
return set(python_value).issubset(user_response)

if self.operator == self.EQUALS:
return set(python_value) == set(user_response)

if isinstance(python_value, six.string_types) \
and isinstance(user_response, six.string_types):
if self.operator == self.CONTAINS:
Expand Down
Loading

0 comments on commit 543ae39

Please sign in to comment.