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.8'
Browse files Browse the repository at this point in the history
  • Loading branch information
codiebeulaine committed Aug 6, 2018
2 parents 2e3e011 + 043af30 commit 61d6eee
Show file tree
Hide file tree
Showing 11 changed files with 462 additions and 82 deletions.
28 changes: 28 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
CHANGE LOG
==========

6.9.8
-----
- Bug fix: revert survey indexing

6.9.7
-----
- Bug fix: only use pk+name combo to answers stored in the session, not all field names

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
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.9.0
6.9.8
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
69 changes: 53 additions & 16 deletions molo/surveys/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import json
import datetime

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.paginator import EmptyPage, PageNotAnInteger
Expand Down Expand Up @@ -49,6 +50,7 @@
MoloSurveyForm,
PersonalisableMoloSurveyForm,
SurveysFormBuilder,
CHARACTER_COUNT_CHOICE_LIMIT,
)
from .rules import ( # noqa
ArticleTagRule,
Expand All @@ -69,6 +71,16 @@
FooterPage.parent_page_types += ['surveys.TermsAndConditionsIndexPage']


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

@property
def pk_clean_name(self):
return '{}-{}'.format(self.pk, self.clean_name)


class TermsAndConditionsIndexPage(TranslatablePageMixinNotRoutable, MoloPage):
parent_page_types = ['surveys.SurveysIndexPage']
subpage_types = ['core.Footerpage']
Expand Down Expand Up @@ -278,6 +290,15 @@ def save_data(self, request, data):
request.session[self.session_key_data] = json.dumps(
data, cls=DjangoJSONEncoder)

@classmethod
def pk_cleaned_data(cls, fields, cleaned_data):
pk_cleaned_field_data = {}
for field in fields:
if field.clean_name in cleaned_data.keys():
pk_cleaned_field_data[field.pk_clean_name] \
= cleaned_data.get(field.clean_name)
return pk_cleaned_field_data

def serve_questions(self, request):
"""
Implements a simple multi-step form.
Expand Down Expand Up @@ -327,8 +348,10 @@ def serve_questions(self, request):
user=request.user,
)
if prev_form.is_valid():
fields = self.get_form_fields()
# If data for step is valid, update the session
survey_data.update(prev_form.cleaned_data)
survey_data.update(
self.pk_cleaned_data(fields, paginator.new_answers))
self.save_data(request, survey_data)

if prev_step.has_next():
Expand All @@ -354,9 +377,12 @@ def serve_questions(self, request):

# We fill in the missing fields which were skipped with
# a default value
for question in self.get_form_fields():
if question.clean_name not in data:
for question in fields:
if question.pk_clean_name not in data:
form.cleaned_data[question.clean_name] = SKIP
else:
form.cleaned_data[question.clean_name]\
= data[question.pk_clean_name]

self.process_form_submission(form)
del request.session[self.session_key_data]
Expand Down Expand Up @@ -460,7 +486,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 @@ -475,8 +501,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 @@ -535,8 +561,10 @@ 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'),
Expand All @@ -547,10 +575,12 @@ class MoloSurveyFormField(SkipLogicMixin, AdminLabelMixin,
)
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)

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

class Meta(AbstractFormField.Meta):
class Meta(SurveyAbstractFormField.Meta):
pass

def clean(self):
Expand All @@ -565,7 +595,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 @@ -583,6 +613,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 @@ -689,7 +723,7 @@ def serve(self, request, *args, **kwargs):

class PersonalisableSurveyFormField(SkipLogicMixin, AdminLabelMixin,
QuestionPaginationMixin,
AbstractFormField):
SurveyAbstractFormField):
"""
Form field that has a segment assigned.
"""
Expand All @@ -705,12 +739,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 61d6eee

Please sign in to comment.